diff --git a/.circleci/config.yml b/.circleci/config.yml index 86d8e08..f8baf68 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,6 +37,7 @@ jobs: # Explicitly move into the libchisel directory to properly build with features # FIXME: make this work in workspace cd libchisel && cargo build --release --features wabt && cd .. + cd libchisel && cargo build --release --features binaryen && cd .. - run: name: Test command: | @@ -44,3 +45,4 @@ jobs: # Explicitly move into the libchisel directory to properly build with features # FIXME: make this work in workspace cd libchisel && cargo test --features wabt && cd .. + cd libchisel && cargo test --features binaryen && cd .. diff --git a/Cargo.toml b/Cargo.toml index 3b18178..5eb936b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,4 @@ members = [ "chisel", "libchisel" -] \ No newline at end of file +] diff --git a/chisel/Cargo.toml b/chisel/Cargo.toml index 333047e..be3ab93 100644 --- a/chisel/Cargo.toml +++ b/chisel/Cargo.toml @@ -21,3 +21,4 @@ serde_yaml = "0.8.7" [features] default = [] wabt = [ "libchisel/wabt" ] +binaryen = ["libchisel/binaryen"] diff --git a/chisel/src/main.rs b/chisel/src/main.rs index f9237da..d6a2a34 100644 --- a/chisel/src/main.rs +++ b/chisel/src/main.rs @@ -16,6 +16,9 @@ use libchisel::{ snip::*, trimexports::*, trimstartfunc::*, verifyexports::*, verifyimports::*, }; +#[cfg(feature = "binaryen")] +use libchisel::binaryenopt::*; + use clap::{App, Arg, ArgMatches, SubCommand}; use libchisel::*; use parity_wasm::elements::{deserialize_buffer, serialize_to_file, Module}; @@ -269,6 +272,15 @@ fn execute_module(context: &ModuleContext, module: &mut Module) -> bool { is_translator = true; translate_module(module, &DropSection::NamesSection) } + #[cfg(feature = "binaryen")] + "binaryenopt" => { + is_translator = true; + if let Ok(chisel) = BinaryenOptimiser::with_preset(&preset) { + translate_module(module, &chisel) + } else { + Err("binaryenopt: Invalid preset") + } + } _ => Err("Module Not Found"), }; diff --git a/libchisel/Cargo.toml b/libchisel/Cargo.toml index 8e55fe0..dc9098f 100644 --- a/libchisel/Cargo.toml +++ b/libchisel/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["webassembly", "wasm", "blockchain", "ethereum"] edition = "2018" [dependencies] +binaryen = { version = "0.8", optional = true } parity-wasm = "^0.40.1" rustc-hex = "1.0" failure = "0.1.5" diff --git a/libchisel/src/binaryenopt.rs b/libchisel/src/binaryenopt.rs new file mode 100644 index 0000000..06beab1 --- /dev/null +++ b/libchisel/src/binaryenopt.rs @@ -0,0 +1,142 @@ +use super::{ChiselModule, ModuleError, ModuleKind, ModulePreset, ModuleTranslator}; +use crate::utils::SerializationHelpers; +use parity_wasm::elements::*; + +// FIXME: change level names +pub enum BinaryenOptimiser { + O0, // Baseline aka no changes + O1, + O2, + O3, + O4, + Os, + Oz, +} + +impl<'a> ChiselModule<'a> for BinaryenOptimiser { + type ObjectReference = &'a dyn ModuleTranslator; + + fn id(&'a self) -> String { + "binaryenopt".to_string() + } + + fn kind(&'a self) -> ModuleKind { + ModuleKind::Translator + } + + fn as_abstract(&'a self) -> Self::ObjectReference { + self as Self::ObjectReference + } +} + +impl ModulePreset for BinaryenOptimiser { + fn with_preset(preset: &str) -> Result { + match preset { + "O0" => Ok(BinaryenOptimiser::O0), + "O1" => Ok(BinaryenOptimiser::O1), + "O2" => Ok(BinaryenOptimiser::O2), + "O3" => Ok(BinaryenOptimiser::O3), + "O4" => Ok(BinaryenOptimiser::O4), + "Os" => Ok(BinaryenOptimiser::Os), + "Oz" => Ok(BinaryenOptimiser::Oz), + _ => Err(()), + } + } +} + +impl ModuleTranslator for BinaryenOptimiser { + fn translate_inplace(&self, module: &mut Module) -> Result { + Err(ModuleError::NotSupported) + } + + fn translate(&self, module: &Module) -> Result, ModuleError> { + let has_names_section = module.has_names_section(); + + // FIXME: could just move this into `BinaryenOptimiser` + let config = match &self { + BinaryenOptimiser::O0 => binaryen::CodegenConfig { + optimization_level: 0, + shrink_level: 0, + debug_info: has_names_section, + }, + BinaryenOptimiser::O1 => binaryen::CodegenConfig { + optimization_level: 1, + shrink_level: 0, + debug_info: has_names_section, + }, + BinaryenOptimiser::O2 => binaryen::CodegenConfig { + optimization_level: 2, + shrink_level: 0, + debug_info: has_names_section, + }, + BinaryenOptimiser::O3 => binaryen::CodegenConfig { + optimization_level: 3, + shrink_level: 0, + debug_info: has_names_section, + }, + BinaryenOptimiser::O4 => binaryen::CodegenConfig { + optimization_level: 4, + shrink_level: 0, + debug_info: has_names_section, + }, + BinaryenOptimiser::Os => binaryen::CodegenConfig { + optimization_level: 2, + shrink_level: 1, + debug_info: has_names_section, + }, + BinaryenOptimiser::Oz => binaryen::CodegenConfig { + optimization_level: 2, + shrink_level: 2, + debug_info: has_names_section, + }, + }; + + let serialized = module.clone().to_vec()?; + let output = binaryen_optimiser(&serialized, &config)?; + let output = Module::from_slice(&output)?; + Ok(Some(output)) + } +} + +fn binaryen_optimiser( + input: &[u8], + config: &binaryen::CodegenConfig, +) -> Result, ModuleError> { + match binaryen::Module::read(&input) { + Ok(module) => { + // NOTE: this is a global setting... + binaryen::set_global_codegen_config(&config); + module.optimize(); + Ok(module.write()) + } + Err(_) => Err(ModuleError::Custom( + "Failed to deserialise binary with binaryen".to_string(), + )), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn smoke_test_o0() { + let input: Vec = vec![ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, 0x00, 0x00, + 0x03, 0x02, 0x01, 0x00, 0x07, 0x08, 0x01, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, + 0x08, 0x01, 0x00, 0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b, + ]; + + let expected: Vec = vec![ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, 0x00, 0x00, + 0x03, 0x02, 0x01, 0x00, 0x07, 0x08, 0x01, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, + 0x0a, 0x05, 0x01, 0x03, 0x00, 0x01, 0x0b, + ]; + + let module = Module::from_slice(&input).unwrap(); + let translator = BinaryenOptimiser::with_preset("O0").unwrap(); + let result = translator.translate(&module).unwrap().unwrap(); + let serialized = result.to_vec().unwrap(); + assert_eq!(expected, serialized); + } +} diff --git a/libchisel/src/lib.rs b/libchisel/src/lib.rs index 5635579..aa7b2c2 100644 --- a/libchisel/src/lib.rs +++ b/libchisel/src/lib.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "binaryen")] +extern crate binaryen; extern crate parity_wasm; extern crate rustc_hex; @@ -5,6 +7,8 @@ use parity_wasm::elements::Module; pub mod imports; +#[cfg(feature = "binaryen")] +pub mod binaryenopt; pub mod checkfloat; pub mod checkstartfunc; pub mod deployer;