diff --git a/opt/src/main.rs b/opt/src/main.rs index a1710ea..5264be7 100644 --- a/opt/src/main.rs +++ b/opt/src/main.rs @@ -2,116 +2,7 @@ extern crate parity_wasm; extern crate wasm_utils; use std::env; -use std::collections::HashSet; -use parity_wasm::elements; -use wasm_utils::symbols::{Symbol, expand_symbols, push_code_symbols, resolve_function}; -pub fn update_call_index(opcodes: &mut elements::Opcodes, eliminated_indices: &[usize]) { - 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, eliminated_indices) - }, - &mut Call(ref mut call_index) => { - let totalle = eliminated_indices.iter().take_while(|i| (**i as u32) < *call_index).count(); - println!("rewired call {} -> call {}", *call_index, *call_index - totalle as u32); - *call_index -= totalle as u32; - }, - _ => { }, - } - } -} - -/// Updates global references considering the _ordered_ list of eliminated indices -pub fn update_global_index(opcodes: &mut Vec, eliminated_indices: &[usize]) { - use parity_wasm::elements::Opcode::*; - for opcode in opcodes.iter_mut() { - match opcode { - &mut Block(_, ref mut block) | &mut If(_, ref mut block) | &mut Loop(_, ref mut block) => { - update_global_index(block.elements_mut(), eliminated_indices) - }, - &mut GetGlobal(ref mut index) | &mut SetGlobal(ref mut index) => { - let totalle = eliminated_indices.iter().take_while(|i| (**i as u32) < *index).count(); - println!("rewired global {} -> global {}", *index, *index - totalle as u32); - *index -= totalle as u32; - }, - _ => { }, - } - } -} - -pub fn import_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::ImportSection> { - for section in module.sections_mut() { - match section { - &mut elements::Section::Import(ref mut sect) => { - return Some(sect); - }, - _ => { } - } - } - None -} - -pub fn global_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::GlobalSection> { - for section in module.sections_mut() { - match section { - &mut elements::Section::Global(ref mut sect) => { - return Some(sect); - }, - _ => { } - } - } - None -} - -pub fn functions_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::FunctionsSection> { - for section in module.sections_mut() { - match section { - &mut elements::Section::Function(ref mut sect) => { - return Some(sect); - }, - _ => { } - } - } - None -} - -pub fn code_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::CodeSection> { - for section in module.sections_mut() { - match section { - &mut elements::Section::Code(ref mut sect) => { - return Some(sect); - }, - _ => { } - } - } - None -} - -pub fn export_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::ExportSection> { - for section in module.sections_mut() { - match section { - &mut elements::Section::Export(ref mut sect) => { - return Some(sect); - }, - _ => { } - } - } - None -} - -pub fn type_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::TypeSection> { - for section in module.sections_mut() { - match section { - &mut elements::Section::Type(ref mut sect) => { - return Some(sect); - }, - _ => { } - } - } - None -} fn main() { @@ -121,243 +12,12 @@ fn main() { return; } - // Loading module let mut module = parity_wasm::deserialize_file(&args[1]).unwrap(); - // WebAssembly exports optimizer - // Motivation: emscripten compiler backend compiles in many unused exports - // 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"]; - - // Algo starts from the top, listing all items that should stay - let mut stay = HashSet::new(); - for (index, entry) in module.export_section().expect("Export section to exist").entries().iter().enumerate() { - if used_exports.iter().find(|e| **e == entry.field()).is_some() { - stay.insert(Symbol::Export(index)); - } - } - - // All symbols used in data/element segments are also should be preserved - let mut init_symbols = Vec::new(); - if let Some(data_section) = module.data_section() { - for segment in data_section.entries() { - push_code_symbols(&module, segment.offset().code(), &mut init_symbols); - } - } - if let Some(elements_section) = module.elements_section() { - for segment in elements_section.entries() { - push_code_symbols(&module, segment.offset().code(), &mut init_symbols); - for func_index in segment.members() { - stay.insert(resolve_function(&module, *func_index)); - } - } - } - for symbol in init_symbols.drain(..) { stay.insert(symbol); } - - // Call function which will traverse the list recursively, filling stay with all symbols - // that are already used by those which already there - expand_symbols(&mut module, &mut stay); - - for symbol in stay.iter() { - println!("symbol to stay: {:?}", symbol); - } - - // Keep track of referreable symbols to rewire calls/globals - let mut eliminated_funcs = Vec::new(); - let mut eliminated_globals = Vec::new(); - let mut eliminated_types = Vec::new(); - - // First, iterate through types - let mut index = 0; - let mut old_index = 0; - - { - loop { - if type_section(&mut module).expect("Functons section to exist").types_mut().len() == index { break; } - - if stay.contains(&Symbol::Type(old_index)) { - index += 1; - } else { - type_section(&mut module).expect("Code section to exist").types_mut().remove(index); - eliminated_types.push(old_index); - println!("Eliminated type({})", old_index); - } - old_index += 1; - } - } - - // Second, iterate through imports - let mut top_funcs = 0; - let mut top_globals = 0; - - { - index = 0; - old_index = 0; - let imports = import_section(&mut module).expect("Import section to exist"); - loop { - let mut remove = false; - match imports.entries()[index].external() { - &elements::External::Function(_) => { - if stay.contains(&Symbol::Import(old_index)) { - index += 1; - } else { - remove = true; - eliminated_funcs.push(top_funcs); - println!("Eliminated import({}) func({}, {})", old_index, top_funcs, imports.entries()[index].field()); - } - top_funcs += 1; - }, - &elements::External::Global(_) => { - if stay.contains(&Symbol::Import(old_index)) { - index += 1; - } else { - remove = true; - eliminated_globals.push(top_globals); - println!("Eliminated import({}) global({}, {})", old_index, top_globals, imports.entries()[index].field()); - } - top_globals += 1; - }, - _ => { - index += 1; - } - } - if remove { - imports.entries_mut().remove(index); - } - - old_index += 1; - - if index == imports.entries().len() { break; } - } - } - - // Third, iterate through globals - { - let globals = global_section(&mut module).expect("Global section to exist"); - - index = 0; - old_index = 0; - - loop { - if globals.entries_mut().len() == index { break; } - if stay.contains(&Symbol::Global(old_index)) { - index += 1; - } else { - globals.entries_mut().remove(index); - eliminated_globals.push(top_globals + old_index); - println!("Eliminated global({})", top_globals + old_index); - } - old_index += 1; - } - } - - // Forth, delete orphaned functions - index = 0; - old_index = 0; - - loop { - if functions_section(&mut module).expect("Functons section to exist").entries_mut().len() == index { break; } - if stay.contains(&Symbol::Function(old_index)) { - index += 1; - } else { - functions_section(&mut module).expect("Functons section to exist").entries_mut().remove(index); - code_section(&mut module).expect("Code section to exist").bodies_mut().remove(index); - - eliminated_funcs.push(top_funcs + old_index); - println!("Eliminated function({})", top_funcs + old_index); - } - old_index += 1; - } - - // Fivth, eliminate unused exports - { - let exports = export_section(&mut module).expect("Export section to exist"); - - index = 0; - old_index = 0; - - loop { - if exports.entries_mut().len() == index { break; } - if stay.contains(&Symbol::Export(old_index)) { - index += 1; - } else { - println!("Eliminated export({}, {})", old_index, exports.entries_mut()[index].field()); - exports.entries_mut().remove(index); - } - old_index += 1; - } - } - - if eliminated_globals.len() > 0 || eliminated_funcs.len() > 0 || eliminated_types.len() > 0 { - // Finaly, rewire all calls, globals references and types to the new indices - // (only if there is anything to do) - eliminated_globals.sort(); - eliminated_funcs.sort(); - eliminated_types.sort(); - - for section in module.sections_mut() { - match section { - &mut elements::Section::Function(ref mut function_section) => { - for ref mut func_signature in function_section.entries_mut() { - let totalle = eliminated_types.iter().take_while(|i| (**i as u32) < func_signature.type_ref()).count(); - *func_signature.type_ref_mut() -= totalle as u32; - } - }, - &mut elements::Section::Import(ref mut import_section) => { - for ref mut import_entry in import_section.entries_mut() { - if let &mut elements::External::Function(ref mut type_ref) = import_entry.external_mut() { - let totalle = eliminated_types.iter().take_while(|i| (**i as u32) < *type_ref).count(); - *type_ref -= totalle as u32; - } - } - }, - &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(), &eliminated_funcs); - update_global_index(func_body.code_mut().elements_mut(), &eliminated_globals) - } - }, - &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) => { - let totalle = eliminated_funcs.iter().take_while(|i| (**i as u32) < *func_index).count(); - *func_index -= totalle as u32; - }, - &mut elements::Internal::Global(ref mut global_index) => { - let totalle = eliminated_globals.iter().take_while(|i| (**i as u32) < *global_index).count(); - *global_index -= totalle as u32; - }, - _ => {} - } - } - }, - &mut elements::Section::Global(ref mut global_section) => { - for ref mut global_entry in global_section.entries_mut() { - update_global_index(global_entry.init_expr_mut().code_mut(), &eliminated_globals) - } - }, - &mut elements::Section::Data(ref mut data_section) => { - for ref mut segment in data_section.entries_mut() { - update_global_index(segment.offset_mut().code_mut(), &eliminated_globals) - } - }, - &mut elements::Section::Element(ref mut elements_section) => { - for ref mut segment in elements_section.entries_mut() { - update_global_index(segment.offset_mut().code_mut(), &eliminated_globals); - // update all indirect call addresses initial values - for func_index in segment.members_mut() { - let totalle = eliminated_funcs.iter().take_while(|i| (**i as u32) < *func_index).count(); - *func_index -= totalle as u32; - } - } - }, - _ => { } - } - } - } + // Invoke optimizer + // Contract is supposed to have only these functions as public api + // All other symbols not usable by this list is optimized away + wasm_utils::optimize(&mut module, vec!["_call"]); parity_wasm::serialize_to_file(&args[2], module).unwrap(); } diff --git a/src/lib.rs b/src/lib.rs index 148f964..8d4d9bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ extern crate parity_wasm; -pub mod symbols; \ No newline at end of file +mod optimizer; +pub mod symbols; + +pub use optimizer::optimize; \ No newline at end of file diff --git a/src/optimizer.rs b/src/optimizer.rs new file mode 100644 index 0000000..db59f75 --- /dev/null +++ b/src/optimizer.rs @@ -0,0 +1,350 @@ +use std::collections::HashSet; +use parity_wasm::elements; + +use symbols::{Symbol, expand_symbols, push_code_symbols, resolve_function}; + +pub fn optimize( + module: &mut elements::Module, // Module to optimize + used_exports: Vec<&str>, // List of only exports that will be usable after optimization +) { + // WebAssembly exports optimizer + // Motivation: emscripten compiler backend compiles in many unused exports + // which in turn compile in unused imports and leaves unused functions + + // Algo starts from the top, listing all items that should stay + let mut stay = HashSet::new(); + for (index, entry) in module.export_section().expect("Export section to exist").entries().iter().enumerate() { + if used_exports.iter().find(|e| **e == entry.field()).is_some() { + stay.insert(Symbol::Export(index)); + } + } + + // All symbols used in data/element segments are also should be preserved + let mut init_symbols = Vec::new(); + if let Some(data_section) = module.data_section() { + for segment in data_section.entries() { + push_code_symbols(&module, segment.offset().code(), &mut init_symbols); + } + } + if let Some(elements_section) = module.elements_section() { + for segment in elements_section.entries() { + push_code_symbols(&module, segment.offset().code(), &mut init_symbols); + for func_index in segment.members() { + stay.insert(resolve_function(&module, *func_index)); + } + } + } + for symbol in init_symbols.drain(..) { stay.insert(symbol); } + + // Call function which will traverse the list recursively, filling stay with all symbols + // that are already used by those which already there + expand_symbols(module, &mut stay); + + for symbol in stay.iter() { + println!("symbol to stay: {:?}", symbol); + } + + // Keep track of referreable symbols to rewire calls/globals + let mut eliminated_funcs = Vec::new(); + let mut eliminated_globals = Vec::new(); + let mut eliminated_types = Vec::new(); + + // First, iterate through types + let mut index = 0; + let mut old_index = 0; + + { + loop { + if type_section(module).expect("Functons section to exist").types_mut().len() == index { break; } + + if stay.contains(&Symbol::Type(old_index)) { + index += 1; + } else { + type_section(module).expect("Code section to exist").types_mut().remove(index); + eliminated_types.push(old_index); + println!("Eliminated type({})", old_index); + } + old_index += 1; + } + } + + // Second, iterate through imports + let mut top_funcs = 0; + let mut top_globals = 0; + + { + index = 0; + old_index = 0; + let imports = import_section(module).expect("Import section to exist"); + loop { + let mut remove = false; + match imports.entries()[index].external() { + &elements::External::Function(_) => { + if stay.contains(&Symbol::Import(old_index)) { + index += 1; + } else { + remove = true; + eliminated_funcs.push(top_funcs); + println!("Eliminated import({}) func({}, {})", old_index, top_funcs, imports.entries()[index].field()); + } + top_funcs += 1; + }, + &elements::External::Global(_) => { + if stay.contains(&Symbol::Import(old_index)) { + index += 1; + } else { + remove = true; + eliminated_globals.push(top_globals); + println!("Eliminated import({}) global({}, {})", old_index, top_globals, imports.entries()[index].field()); + } + top_globals += 1; + }, + _ => { + index += 1; + } + } + if remove { + imports.entries_mut().remove(index); + } + + old_index += 1; + + if index == imports.entries().len() { break; } + } + } + + // Third, iterate through globals + { + let globals = global_section(module).expect("Global section to exist"); + + index = 0; + old_index = 0; + + loop { + if globals.entries_mut().len() == index { break; } + if stay.contains(&Symbol::Global(old_index)) { + index += 1; + } else { + globals.entries_mut().remove(index); + eliminated_globals.push(top_globals + old_index); + println!("Eliminated global({})", top_globals + old_index); + } + old_index += 1; + } + } + + // Forth, delete orphaned functions + index = 0; + old_index = 0; + + loop { + if functions_section(module).expect("Functons section to exist").entries_mut().len() == index { break; } + if stay.contains(&Symbol::Function(old_index)) { + index += 1; + } else { + functions_section(module).expect("Functons section to exist").entries_mut().remove(index); + code_section(module).expect("Code section to exist").bodies_mut().remove(index); + + eliminated_funcs.push(top_funcs + old_index); + println!("Eliminated function({})", top_funcs + old_index); + } + old_index += 1; + } + + // Fivth, eliminate unused exports + { + let exports = export_section(module).expect("Export section to exist"); + + index = 0; + old_index = 0; + + loop { + if exports.entries_mut().len() == index { break; } + if stay.contains(&Symbol::Export(old_index)) { + index += 1; + } else { + println!("Eliminated export({}, {})", old_index, exports.entries_mut()[index].field()); + exports.entries_mut().remove(index); + } + old_index += 1; + } + } + + if eliminated_globals.len() > 0 || eliminated_funcs.len() > 0 || eliminated_types.len() > 0 { + // Finaly, rewire all calls, globals references and types to the new indices + // (only if there is anything to do) + eliminated_globals.sort(); + eliminated_funcs.sort(); + eliminated_types.sort(); + + for section in module.sections_mut() { + match section { + &mut elements::Section::Function(ref mut function_section) => { + for ref mut func_signature in function_section.entries_mut() { + let totalle = eliminated_types.iter().take_while(|i| (**i as u32) < func_signature.type_ref()).count(); + *func_signature.type_ref_mut() -= totalle as u32; + } + }, + &mut elements::Section::Import(ref mut import_section) => { + for ref mut import_entry in import_section.entries_mut() { + if let &mut elements::External::Function(ref mut type_ref) = import_entry.external_mut() { + let totalle = eliminated_types.iter().take_while(|i| (**i as u32) < *type_ref).count(); + *type_ref -= totalle as u32; + } + } + }, + &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(), &eliminated_funcs); + update_global_index(func_body.code_mut().elements_mut(), &eliminated_globals) + } + }, + &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) => { + let totalle = eliminated_funcs.iter().take_while(|i| (**i as u32) < *func_index).count(); + *func_index -= totalle as u32; + }, + &mut elements::Internal::Global(ref mut global_index) => { + let totalle = eliminated_globals.iter().take_while(|i| (**i as u32) < *global_index).count(); + *global_index -= totalle as u32; + }, + _ => {} + } + } + }, + &mut elements::Section::Global(ref mut global_section) => { + for ref mut global_entry in global_section.entries_mut() { + update_global_index(global_entry.init_expr_mut().code_mut(), &eliminated_globals) + } + }, + &mut elements::Section::Data(ref mut data_section) => { + for ref mut segment in data_section.entries_mut() { + update_global_index(segment.offset_mut().code_mut(), &eliminated_globals) + } + }, + &mut elements::Section::Element(ref mut elements_section) => { + for ref mut segment in elements_section.entries_mut() { + update_global_index(segment.offset_mut().code_mut(), &eliminated_globals); + // update all indirect call addresses initial values + for func_index in segment.members_mut() { + let totalle = eliminated_funcs.iter().take_while(|i| (**i as u32) < *func_index).count(); + *func_index -= totalle as u32; + } + } + }, + _ => { } + } + } + } + +} + + +pub fn update_call_index(opcodes: &mut elements::Opcodes, eliminated_indices: &[usize]) { + 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, eliminated_indices) + }, + &mut Call(ref mut call_index) => { + let totalle = eliminated_indices.iter().take_while(|i| (**i as u32) < *call_index).count(); + println!("rewired call {} -> call {}", *call_index, *call_index - totalle as u32); + *call_index -= totalle as u32; + }, + _ => { }, + } + } +} + +/// Updates global references considering the _ordered_ list of eliminated indices +pub fn update_global_index(opcodes: &mut Vec, eliminated_indices: &[usize]) { + use parity_wasm::elements::Opcode::*; + for opcode in opcodes.iter_mut() { + match opcode { + &mut Block(_, ref mut block) | &mut If(_, ref mut block) | &mut Loop(_, ref mut block) => { + update_global_index(block.elements_mut(), eliminated_indices) + }, + &mut GetGlobal(ref mut index) | &mut SetGlobal(ref mut index) => { + let totalle = eliminated_indices.iter().take_while(|i| (**i as u32) < *index).count(); + println!("rewired global {} -> global {}", *index, *index - totalle as u32); + *index -= totalle as u32; + }, + _ => { }, + } + } +} + +pub fn import_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::ImportSection> { + for section in module.sections_mut() { + match section { + &mut elements::Section::Import(ref mut sect) => { + return Some(sect); + }, + _ => { } + } + } + None +} + +pub fn global_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::GlobalSection> { + for section in module.sections_mut() { + match section { + &mut elements::Section::Global(ref mut sect) => { + return Some(sect); + }, + _ => { } + } + } + None +} + +pub fn functions_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::FunctionsSection> { + for section in module.sections_mut() { + match section { + &mut elements::Section::Function(ref mut sect) => { + return Some(sect); + }, + _ => { } + } + } + None +} + +pub fn code_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::CodeSection> { + for section in module.sections_mut() { + match section { + &mut elements::Section::Code(ref mut sect) => { + return Some(sect); + }, + _ => { } + } + } + None +} + +pub fn export_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::ExportSection> { + for section in module.sections_mut() { + match section { + &mut elements::Section::Export(ref mut sect) => { + return Some(sect); + }, + _ => { } + } + } + None +} + +pub fn type_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::TypeSection> { + for section in module.sections_mut() { + match section { + &mut elements::Section::Type(ref mut sect) => { + return Some(sect); + }, + _ => { } + } + } + None +} \ No newline at end of file