diff --git a/cli/build/main.rs b/cli/build/main.rs index 7b90d4f..38df600 100644 --- a/cli/build/main.rs +++ b/cli/build/main.rs @@ -9,13 +9,11 @@ extern crate pwasm_utils_cli as logger; mod source; use std::{fs, io}; -use std::io::Write; use std::path::PathBuf; use clap::{App, Arg}; use parity_wasm::elements; - -use utils::{CREATE_SYMBOL, CALL_SYMBOL, ununderscore_funcs, externalize_mem, shrink_unknown_stack}; +use utils::{build, BuildError, SourceTarget}; #[derive(Debug)] pub enum Error { @@ -23,38 +21,18 @@ pub enum Error { FailedToCopy(String), Decoding(elements::Error, String), Encoding(elements::Error), - Packing(utils::PackingError), - Optimizer, -} - -impl From for Error { - fn from(err: io::Error) -> Self { - Error::Io(err) - } -} - -impl From for Error { - fn from(_err: utils::OptimizerError) -> Self { - Error::Optimizer - } -} - -impl From for Error { - fn from(err: utils::PackingError) -> Self { - Error::Packing(err) - } + Build(BuildError), } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - use Error::*; + use self::Error::*; match *self { Io(ref io) => write!(f, "Generic i/o error: {}", io), FailedToCopy(ref msg) => write!(f, "{}. Have you tried to run \"cargo build\"?", msg), Decoding(ref err, ref file) => write!(f, "Decoding error ({}). Must be a valid wasm file {}. Pointed wrong file?", err, file), Encoding(ref err) => write!(f, "Encoding error ({}). Almost impossible to happen, no free disk space?", err), - Optimizer => write!(f, "Optimization error due to missing export section. Pointed wrong file?"), - Packing(ref e) => write!(f, "Packing failed due to module structure error: {}. Sure used correct libraries for building contracts?", e), + Build(ref err) => write!(f, "Build error: {}", err) } } } @@ -70,8 +48,8 @@ pub fn process_output(input: &source::SourceInput) -> Result<(), Error> { let wasm_name = input.bin_name().to_string().replace("-", "_"); cargo_path.push( match input.target() { - source::SourceTarget::Emscripten => source::EMSCRIPTEN_TRIPLET, - source::SourceTarget::Unknown => source::UNKNOWN_TRIPLET, + SourceTarget::Emscripten => source::EMSCRIPTEN_TRIPLET, + SourceTarget::Unknown => source::UNKNOWN_TRIPLET, } ); cargo_path.push("release"); @@ -87,14 +65,6 @@ pub fn process_output(input: &source::SourceInput) -> 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 do_main() -> Result<(), Error> { logger::init_log(); @@ -166,71 +136,48 @@ fn do_main() -> Result<(), Error> { let path = wasm_path(&source_input); - let mut module = parity_wasm::deserialize_file(&path) + let module = parity_wasm::deserialize_file(&path) .map_err(|e| Error::Decoding(e, path.to_string()))?; - if let source::SourceTarget::Emscripten = source_input.target() { - module = ununderscore_funcs(module); - } - - if let source::SourceTarget::Unknown = source_input.target() { - // 49152 is 48kb! - if matches.is_present("enforce_stack_adjustment") { - let stack_size: u32 = matches.value_of("shrink_stack").unwrap_or_else(|| "49152").parse().expect("New stack size is not valid u32"); - assert!(stack_size <= 1024*1024); - let (new_module, new_stack_top) = shrink_unknown_stack(module, 1024 * 1024 - stack_size); - module = new_module; - let mut stack_top_page = new_stack_top / 65536; - if new_stack_top % 65536 > 0 { stack_top_page += 1 }; - module = externalize_mem(module, Some(stack_top_page), 16); - } else { - module = externalize_mem(module, None, 16); - } - } - - if let Some(runtime_type) = matches.value_of("runtime_type") { - let runtime_type: &[u8] = runtime_type.as_bytes(); - if runtime_type.len() != 4 { + let runtime_type_version = if let (Some(runtime_type), Some(runtime_version)) + = (matches.value_of("runtime_type"), matches.value_of("runtime_version")) { + let mut ty: [u8; 4] = Default::default(); + let runtime_bytes = runtime_type.as_bytes(); + if runtime_bytes.len() != 4 { panic!("--runtime-type should be equal to 4 bytes"); } - let runtime_version: u32 = matches.value_of("runtime_version").unwrap_or("1").parse() + ty.copy_from_slice(runtime_bytes); + let version: u32 = runtime_version.parse() .expect("--runtime-version should be a positive integer"); - module = utils::inject_runtime_type(module, &runtime_type, runtime_version); - } + Some((ty, version)) + } else { + None + }; - let mut ctor_module = module.clone(); - - let mut public_api_entries = matches.value_of("public_api") + let public_api_entries = matches.value_of("public_api") .map(|val| val.split(",").collect()) .unwrap_or(Vec::new()); - public_api_entries.push(CALL_SYMBOL); - if !matches.is_present("skip_optimization") { - utils::optimize( - &mut module, - public_api_entries, - )?; - } + + let (module, ctor_module) = build( + module, + true, + source_input.target(), + runtime_type_version, + &public_api_entries, + matches.is_present("enforce_stack_adjustment"), + matches.value_of("shrink_stack").unwrap_or_else(|| "49152").parse() + .expect("New stack size is not valid u32"), + matches.is_present("skip_optimization"), + ).map_err(Error::Build)?; if let Some(save_raw_path) = matches.value_of("save_raw") { parity_wasm::serialize_to_file(save_raw_path, module.clone()).map_err(Error::Encoding)?; } - let raw_module = parity_wasm::serialize(module).map_err(Error::Encoding)?; - - // 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") { - utils::optimize(&mut ctor_module, vec![CREATE_SYMBOL])?; - } - let ctor_module = utils::pack_instance(raw_module, ctor_module)?; - parity_wasm::serialize_to_file(&path, ctor_module).map_err(Error::Encoding)?; - } else { - let mut file = fs::File::create(&path)?; - file.write_all(&raw_module)?; - } - + parity_wasm::serialize_to_file( + &path, + ctor_module.expect("ctor_module can't be None, because 'constructor' argument is set to true in build"), + ).map_err(Error::Encoding)?; Ok(()) } diff --git a/cli/build/source.rs b/cli/build/source.rs index 627bed6..adaa927 100644 --- a/cli/build/source.rs +++ b/cli/build/source.rs @@ -3,12 +3,7 @@ pub const UNKNOWN_TRIPLET: &str = "wasm32-unknown-unknown"; pub const EMSCRIPTEN_TRIPLET: &str = "wasm32-unknown-emscripten"; -/// Target configiration of previous build step -#[derive(Debug, Clone, Copy)] -pub enum SourceTarget { - Emscripten, - Unknown, -} +use utils::SourceTarget; /// Configuration of previous build step (cargo compilation) #[derive(Debug)] @@ -59,4 +54,4 @@ impl<'a> SourceInput<'a> { pub fn target(&self) -> SourceTarget { self.target } -} \ No newline at end of file +} diff --git a/src/build.rs b/src/build.rs new file mode 100644 index 0000000..80c644b --- /dev/null +++ b/src/build.rs @@ -0,0 +1,123 @@ +use std; +use super::{ + CREATE_SYMBOL, + CALL_SYMBOL, + optimize, + pack_instance, + ununderscore_funcs, + externalize_mem, + shrink_unknown_stack, + inject_runtime_type, + PackingError, + OptimizerError, +}; +use parity_wasm; +use parity_wasm::elements; + +#[derive(Debug)] +pub enum Error { + Encoding(elements::Error), + Packing(PackingError), + NoCreateSymbolFound, + Optimizer, +} + +impl From for Error { + fn from(_err: OptimizerError) -> Self { + Error::Optimizer + } +} + +impl From for Error { + fn from(err: PackingError) -> Self { + Error::Packing(err) + } +} + +#[derive(Debug, Clone, Copy)] +pub enum SourceTarget { + Emscripten, + Unknown, +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + use self::Error::*; + match *self { + Encoding(ref err) => write!(f, "Encoding error ({})", err), + Optimizer => write!(f, "Optimization error due to missing export section. Pointed wrong file?"), + Packing(ref e) => write!(f, "Packing failed due to module structure error: {}. Sure used correct libraries for building contracts?", e), + NoCreateSymbolFound => write!(f, "Packing failed: no \"{}\" symbol found?", CREATE_SYMBOL), + } + } +} + +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 + } +} + +pub fn build( + mut module: elements::Module, + constructor: bool, + source_target: SourceTarget, + runtime_type_version: Option<([u8; 4], u32)>, + public_api_entries: &[&str], + enforce_stack_adjustment: bool, + stack_size: u32, + skip_optimization: bool, +) -> Result<(elements::Module, Option), Error> { + + if let SourceTarget::Emscripten = source_target { + module = ununderscore_funcs(module); + } + + if let SourceTarget::Unknown = source_target { + // 49152 is 48kb! + if enforce_stack_adjustment { + assert!(stack_size <= 1024*1024); + let (new_module, new_stack_top) = shrink_unknown_stack(module, 1024 * 1024 - stack_size); + module = new_module; + let mut stack_top_page = new_stack_top / 65536; + if new_stack_top % 65536 > 0 { stack_top_page += 1 }; + module = externalize_mem(module, Some(stack_top_page), 16); + } else { + module = externalize_mem(module, None, 16); + } + } + + if let Some(runtime_type_version) = runtime_type_version { + let (runtime_type, runtime_version) = runtime_type_version; + module = inject_runtime_type(module, runtime_type, runtime_version); + } + + let mut ctor_module = module.clone(); + + let mut public_api_entries = public_api_entries.to_vec(); + public_api_entries.push(CALL_SYMBOL); + if !skip_optimization { + optimize( + &mut module, + public_api_entries, + )?; + } + + if constructor { + if !has_ctor(&ctor_module) { + Err(Error::NoCreateSymbolFound)? + } + if !skip_optimization { + optimize(&mut ctor_module, vec![CREATE_SYMBOL])?; + } + let ctor_module = pack_instance( + parity_wasm::serialize(module.clone()).map_err(Error::Encoding)?, + ctor_module.clone(), + )?; + Ok((module, Some(ctor_module))) + } else { + Ok((module, None)) + } +} diff --git a/src/lib.rs b/src/lib.rs index ca8d421..6d47ff6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ pub static RET_SYMBOL: &'static str = "ret"; pub mod rules; +mod build; mod optimizer; mod gas; mod symbols; @@ -24,6 +25,7 @@ mod runtime_type; pub mod stack_height; +pub use build::{build, SourceTarget, Error as BuildError}; pub use optimizer::{optimize, Error as OptimizerError}; pub use gas::inject_gas_counter; pub use ext::{externalize, externalize_mem, underscore_funcs, ununderscore_funcs, shrink_unknown_stack}; diff --git a/src/runtime_type.rs b/src/runtime_type.rs index 3786fdc..8fe8d7d 100644 --- a/src/runtime_type.rs +++ b/src/runtime_type.rs @@ -2,7 +2,7 @@ use parity_wasm::{elements, builder}; use self::elements::{ Module, GlobalEntry, External, ExportEntry, GlobalType, ValueType, InitExpr, Instruction, Internal }; use byteorder::{ LittleEndian, ByteOrder }; -pub fn inject_runtime_type(module: Module, runtime_type: &[u8], runtime_version: u32) -> Module { +pub fn inject_runtime_type(module: Module, runtime_type: [u8; 4], runtime_version: u32) -> Module { let runtime_type: u32 = LittleEndian::read_u32(&runtime_type); let globals_count: u32 = match module.global_section() { Some(ref section) => section.entries().len() as u32, @@ -33,7 +33,9 @@ mod tests { let mut module = builder::module() .with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Instruction::I32Const(42 as i32)]))) .build(); - module = inject_runtime_type(module, b"emcc", 1); + let mut runtime_type: [u8; 4] = Default::default(); + runtime_type.copy_from_slice(b"emcc"); + module = inject_runtime_type(module, runtime_type, 1); let global_section = module.global_section().expect("Global section expected"); assert_eq!(3, global_section.entries().len()); let export_section = module.export_section().expect("Export section expected");