diff --git a/ext/.gitignore b/ext/.gitignore new file mode 100644 index 0000000..1de5659 --- /dev/null +++ b/ext/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/ext/Cargo.lock b/ext/Cargo.lock new file mode 100644 index 0000000..f3f6fd4 --- /dev/null +++ b/ext/Cargo.lock @@ -0,0 +1,23 @@ +[root] +name = "wasm-ext" +version = "0.1.0" +dependencies = [ + "parity-wasm 0.3.2 (git+https://github.com/nikvolf/parity-wasm)", +] + +[[package]] +name = "byteorder" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "parity-wasm" +version = "0.3.2" +source = "git+https://github.com/nikvolf/parity-wasm#b11f34f2e5410d0773dcf32699d767d7359e4ca9" +dependencies = [ + "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" +"checksum parity-wasm 0.3.2 (git+https://github.com/nikvolf/parity-wasm)" = "" diff --git a/ext/Cargo.toml b/ext/Cargo.toml new file mode 100644 index 0000000..cfaa1c5 --- /dev/null +++ b/ext/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "wasm-ext" +version = "0.1.0" +authors = ["NikVolf "] + +[dependencies] +parity-wasm = { git="https://github.com/nikvolf/parity-wasm" } \ No newline at end of file diff --git a/ext/src/main.rs b/ext/src/main.rs new file mode 100644 index 0000000..7338824 --- /dev/null +++ b/ext/src/main.rs @@ -0,0 +1,113 @@ +extern crate parity_wasm; + +use std::env; +use parity_wasm::{builder, elements}; + +type Insertion = (u32, u32, String); + +pub fn update_call_index(opcodes: &mut elements::Opcodes, original_imports: usize, inserts: &[Insertion]) { + use parity_wasm::elements::Opcode::*; + for opcode in opcodes.elements_mut().iter_mut() { + match opcode { + &mut Block(_, ref mut block) | &mut If(_, ref mut block) | &mut Loop(_, ref mut block) => { + update_call_index(block, original_imports, inserts) + }, + &mut Call(ref mut call_index) => { + if let Some(pos) = inserts.iter().position(|x| x.0 == *call_index) { + *call_index = (original_imports + pos) as u32; + } else if *call_index as usize > original_imports { + *call_index += inserts.len() as u32; + } + }, + _ => { } + } + } +} + +fn main() { + + let args = env::args().collect::>(); + if args.len() != 3 { + println!("Usage: {} input_file.wasm output_file.wasm", args[0]); + return; + } + + // Loading module + let module = parity_wasm::deserialize_file(&args[1]).unwrap(); + + let replaced_funcs = vec!["_free", "_malloc"]; + + // Save import functions number for later + let import_funcs_total = module + .import_section().expect("Import section to exist") + .entries() + .iter() + .filter(|e| if let &elements::External::Function(_) = e.external() { true } else { false }) + .count(); + + // First, we find functions indices that are to be rewired to externals + // Triple is (function_index (callable), type_index, function_name) + let replaces: Vec = replaced_funcs + .into_iter() + .filter_map(|f| { + let export = module + .export_section().expect("Export section to exist") + .entries().iter() + .find(|e| e.field() == f) + .expect("All functions of interest to exist"); + + if let &elements::Internal::Function(func_idx) = export.internal() { + let type_ref = module + .functions_section().expect("Functions section to exist") + .entries()[func_idx as usize - import_funcs_total] + .type_ref(); + + Some((func_idx, type_ref, export.field().to_owned())) + } else { + None + } + }) + .collect(); + + // Second, we duplicate them as import definitions + let mut mbuilder = builder::from_module(module); + for &(_, type_ref, ref field) in replaces.iter() { + mbuilder.push_import( + builder::import() + .module("env") + .field(field) + .external().func(type_ref) + .build() + ); + } + + // Back to mutable access + let mut module = mbuilder.build(); + + // Third, rewire all calls to imported functions and update all other calls indices + for section in module.sections_mut() { + match section { + &mut elements::Section::Code(ref mut code_section) => { + for ref mut func_body in code_section.bodies_mut() { + update_call_index(func_body.code_mut(), import_funcs_total, &replaces); + } + }, + &mut elements::Section::Export(ref mut export_section) => { + for ref mut export in export_section.entries_mut() { + match export.internal_mut() { + &mut elements::Internal::Function(ref mut func_index) => { + if *func_index >= import_funcs_total as u32 { *func_index += replaces.len() as u32; } + }, + _ => {} + } + } + }, + _ => { } + } + } + + // Forth step could be to eliminate now dead code in actual functions + // but it is managed by `wasm-opt` + + parity_wasm::serialize_to_file(&args[2], module).unwrap(); +} diff --git a/opt/src/main.rs b/opt/src/main.rs index f0400a2..52b0ccd 100644 --- a/opt/src/main.rs +++ b/opt/src/main.rs @@ -240,7 +240,7 @@ fn main() { // which in turn compile in unused imports and leaves unused functions // List of exports that are actually used in the managed code - let used_exports = vec!["_call", "_malloc", "_free"]; + let used_exports = vec!["_call"]; // Algo starts from the top, listing all items that should stay let mut stay = HashSet::new();