@@ -23,7 +23,7 @@ use crate::{
2323} ;
2424use anyhow:: { Context , Result } ;
2525use colored:: Colorize ;
26- use parity_wasm:: elements:: { External , MemoryType , Module , Section } ;
26+ use parity_wasm:: elements:: { External , Internal , MemoryType , Module , Section } ;
2727use regex:: Regex ;
2828use std:: {
2929 convert:: TryFrom ,
@@ -86,8 +86,13 @@ pub struct BuildCommand {
8686 /// - It is possible to define the number of optimization passes in the
8787 /// `[package.metadata.contract]` of your `Cargo.toml` as e.g. `optimization-passes = "3"`.
8888 /// The CLI argument always takes precedence over the profile value.
89- #[ structopt( long = "optimization-passes" ) ]
89+ #[ structopt( long) ]
9090 optimization_passes : Option < OptimizationPasses > ,
91+ /// Do not remove symbols (Wasm name section) when optimizing.
92+ ///
93+ /// This is useful if one wants to analyze or debug the optimized binary.
94+ #[ structopt( long) ]
95+ keep_debug_symbols : bool ,
9196}
9297
9398impl BuildCommand {
@@ -118,6 +123,7 @@ impl BuildCommand {
118123 self . build_artifact ,
119124 unstable_flags,
120125 optimization_passes,
126+ self . keep_debug_symbols ,
121127 )
122128 }
123129}
@@ -146,6 +152,7 @@ impl CheckCommand {
146152 BuildArtifacts :: CheckOnly ,
147153 unstable_flags,
148154 OptimizationPasses :: Zero ,
155+ false ,
149156 )
150157 }
151158}
@@ -260,31 +267,43 @@ fn ensure_maximum_memory_pages(module: &mut Module, maximum_allowed_pages: u32)
260267/// Strips all custom sections.
261268///
262269/// Presently all custom sections are not required so they can be stripped safely.
270+ /// The name section is already stripped by `wasm-opt`.
263271fn strip_custom_sections ( module : & mut Module ) {
264- module. sections_mut ( ) . retain ( |section| {
265- !matches ! (
266- section,
267- Section :: Custom ( _) | Section :: Name ( _) | Section :: Reloc ( _)
268- )
269- } ) ;
272+ module. sections_mut ( ) . retain ( |section| match section {
273+ Section :: Reloc ( _) => false ,
274+ Section :: Custom ( custom) if custom. name ( ) != "name" => false ,
275+ _ => true ,
276+ } )
277+ }
278+
279+ /// A contract should export nothing but the "call" and "deploy" functions.
280+ ///
281+ /// Any elements not referenced by these exports become orphaned and are removed by `wasm-opt`.
282+ fn strip_exports ( module : & mut Module ) {
283+ if let Some ( section) = module. export_section_mut ( ) {
284+ section. entries_mut ( ) . retain ( |entry| {
285+ matches ! ( entry. internal( ) , Internal :: Function ( _) )
286+ && ( entry. field ( ) == "call" || entry. field ( ) == "deploy" )
287+ } )
288+ }
289+ }
290+
291+ /// Load and parse a wasm file from disk.
292+ fn load_module < P : AsRef < Path > > ( path : P ) -> Result < Module > {
293+ let path = path. as_ref ( ) ;
294+ parity_wasm:: deserialize_file ( path) . context ( format ! (
295+ "Loading of wasm module at '{}' failed" ,
296+ path. display( ) ,
297+ ) )
270298}
271299
272300/// Performs required post-processing steps on the wasm artifact.
273301fn post_process_wasm ( crate_metadata : & CrateMetadata ) -> Result < ( ) > {
274302 // Deserialize wasm module from a file.
275303 let mut module =
276- parity_wasm:: deserialize_file ( & crate_metadata. original_wasm ) . context ( format ! (
277- "Loading original wasm file '{}'" ,
278- crate_metadata. original_wasm. display( )
279- ) ) ?;
280-
281- // Perform optimization.
282- //
283- // In practice only tree-shaking is performed, i.e transitively removing all symbols that are
284- // NOT used by the specified entrypoints.
285- if pwasm_utils:: optimize ( & mut module, [ "call" , "deploy" ] . to_vec ( ) ) . is_err ( ) {
286- anyhow:: bail!( "Optimizer failed" ) ;
287- }
304+ load_module ( & crate_metadata. original_wasm ) . context ( "Loading of original wasm failed" ) ?;
305+
306+ strip_exports ( & mut module) ;
288307 ensure_maximum_memory_pages ( & mut module, MAX_MEMORY_PAGES ) ?;
289308 strip_custom_sections ( & mut module) ;
290309
@@ -306,6 +325,7 @@ fn post_process_wasm(crate_metadata: &CrateMetadata) -> Result<()> {
306325fn optimize_wasm (
307326 crate_metadata : & CrateMetadata ,
308327 optimization_passes : OptimizationPasses ,
328+ keep_debug_symbols : bool ,
309329) -> Result < OptimizationResult > {
310330 let mut dest_optimized = crate_metadata. dest_wasm . clone ( ) ;
311331 dest_optimized. set_file_name ( format ! (
@@ -316,6 +336,7 @@ fn optimize_wasm(
316336 crate_metadata. dest_wasm . as_os_str ( ) ,
317337 dest_optimized. as_os_str ( ) ,
318338 optimization_passes,
339+ keep_debug_symbols,
319340 ) ?;
320341
321342 if !dest_optimized. exists ( ) {
@@ -348,6 +369,7 @@ fn do_optimization(
348369 dest_wasm : & OsStr ,
349370 dest_optimized : & OsStr ,
350371 optimization_level : OptimizationPasses ,
372+ keep_debug_symbols : bool ,
351373) -> Result < ( ) > {
352374 // check `wasm-opt` is installed
353375 let which = which:: which ( "wasm-opt" ) ;
@@ -379,23 +401,26 @@ fn do_optimization(
379401 "Optimization level passed to wasm-opt: {}" ,
380402 optimization_level
381403 ) ;
382- let output = Command :: new ( wasm_opt_path)
404+ let mut command = Command :: new ( wasm_opt_path) ;
405+ command
383406 . arg ( dest_wasm)
384407 . arg ( format ! ( "-O{}" , optimization_level) )
385408 . arg ( "-o" )
386409 . arg ( dest_optimized)
387410 // the memory in our module is imported, `wasm-opt` needs to be told that
388411 // the memory is initialized to zeroes, otherwise it won't run the
389412 // memory-packing pre-pass.
390- . arg ( "--zero-filled-memory" )
391- . output ( )
392- . map_err ( |err| {
393- anyhow:: anyhow!(
394- "Executing {} failed with {:?}" ,
395- wasm_opt_path. display( ) ,
396- err
397- )
398- } ) ?;
413+ . arg ( "--zero-filled-memory" ) ;
414+ if keep_debug_symbols {
415+ command. arg ( "-g" ) ;
416+ }
417+ let output = command. output ( ) . map_err ( |err| {
418+ anyhow:: anyhow!(
419+ "Executing {} failed with {:?}" ,
420+ wasm_opt_path. display( ) ,
421+ err
422+ )
423+ } ) ?;
399424
400425 if !output. status . success ( ) {
401426 let err = str:: from_utf8 ( & output. stderr )
@@ -529,6 +554,7 @@ pub(crate) fn execute(
529554 build_artifact : BuildArtifacts ,
530555 unstable_flags : UnstableFlags ,
531556 optimization_passes : OptimizationPasses ,
557+ keep_debug_symbols : bool ,
532558) -> Result < BuildResult > {
533559 let crate_metadata = CrateMetadata :: collect ( manifest_path) ?;
534560
@@ -557,7 +583,8 @@ pub(crate) fn execute(
557583 format!( "[3/{}]" , build_artifact. steps( ) ) . bold( ) ,
558584 "Optimizing wasm file" . bright_green( ) . bold( )
559585 ) ;
560- let optimization_result = optimize_wasm ( & crate_metadata, optimization_passes) ?;
586+ let optimization_result =
587+ optimize_wasm ( & crate_metadata, optimization_passes, keep_debug_symbols) ?;
561588
562589 Ok ( optimization_result)
563590 } ;
@@ -600,7 +627,7 @@ pub(crate) fn execute(
600627mod tests_ci_only {
601628 use super :: { assert_compatible_ink_dependencies, check_wasm_opt_version_compatibility} ;
602629 use crate :: {
603- cmd:: BuildCommand ,
630+ cmd:: { build :: load_module , BuildCommand } ,
604631 util:: tests:: { with_new_contract_project, with_tmp_dir} ,
605632 workspace:: Manifest ,
606633 BuildArtifacts , ManifestPath , OptimizationPasses , UnstableFlags , UnstableOptions ,
@@ -631,6 +658,13 @@ mod tests_ci_only {
631658 . expect ( "writing manifest failed" ) ;
632659 }
633660
661+ fn has_debug_symbols < P : AsRef < Path > > ( p : P ) -> bool {
662+ load_module ( p)
663+ . unwrap ( )
664+ . custom_sections ( )
665+ . any ( |e| e. name ( ) == "name" )
666+ }
667+
634668 /// Creates an executable `wasm-opt-mocked` file which outputs
635669 /// "wasm-opt version `version`".
636670 ///
@@ -660,6 +694,7 @@ mod tests_ci_only {
660694 BuildArtifacts :: CodeOnly ,
661695 UnstableFlags :: default ( ) ,
662696 OptimizationPasses :: default ( ) ,
697+ false ,
663698 )
664699 . expect ( "build failed" ) ;
665700
@@ -682,6 +717,10 @@ mod tests_ci_only {
682717 // our optimized contract template should always be below 3k.
683718 assert ! ( optimized_size < 3.0 ) ;
684719
720+ // we specified that debug symbols should be removed
721+ // original code should have some but the optimized version should have them removed
722+ assert ! ( !has_debug_symbols( & res. dest_wasm. unwrap( ) ) ) ;
723+
685724 Ok ( ( ) )
686725 } )
687726 }
@@ -699,6 +738,7 @@ mod tests_ci_only {
699738 BuildArtifacts :: CheckOnly ,
700739 UnstableFlags :: default ( ) ,
701740 OptimizationPasses :: default ( ) ,
741+ false ,
702742 )
703743 . expect ( "build failed" ) ;
704744
@@ -731,6 +771,7 @@ mod tests_ci_only {
731771
732772 // we choose zero optimization passes as the "cli" parameter
733773 optimization_passes : Some ( OptimizationPasses :: Zero ) ,
774+ keep_debug_symbols : false ,
734775 } ;
735776
736777 // when
@@ -740,17 +781,14 @@ mod tests_ci_only {
740781 . expect ( "no optimization result available" ) ;
741782
742783 // then
743- // we have to truncate here to account for a possible small delta
744- // in the floating point numbers
745- let optimized_size = optimization. optimized_size . trunc ( ) ;
746- let original_size = optimization. original_size . trunc ( ) ;
784+ // The size does not exactly match the original size even without optimization
785+ // passed because there is still some post processing happening.
786+ let size_diff = optimization. original_size - optimization. optimized_size ;
747787 assert ! (
748- optimized_size == original_size,
749- "The optimized size {:?} differs from the original size {:?}" ,
750- optimized_size,
751- original_size
788+ 0.0 < size_diff && size_diff < 10.0 ,
789+ "The optimized size savings are larger than allowed or negative: {}" ,
790+ size_diff,
752791 ) ;
753-
754792 Ok ( ( ) )
755793 } )
756794 }
@@ -771,6 +809,7 @@ mod tests_ci_only {
771809
772810 // we choose no optimization passes as the "cli" parameter
773811 optimization_passes : None ,
812+ keep_debug_symbols : false ,
774813 } ;
775814
776815 // when
@@ -780,15 +819,13 @@ mod tests_ci_only {
780819 . expect ( "no optimization result available" ) ;
781820
782821 // then
783- // we have to truncate here to account for a possible small delta
784- // in the floating point numbers
785- let optimized_size = optimization. optimized_size . trunc ( ) ;
786- let original_size = optimization. original_size . trunc ( ) ;
822+ // The size does not exactly match the original size even without optimization
823+ // passed because there is still some post processing happening.
824+ let size_diff = optimization. original_size - optimization. optimized_size ;
787825 assert ! (
788- optimized_size < original_size,
789- "The optimized size DOES NOT {:?} differ from the original size {:?}" ,
790- optimized_size,
791- original_size
826+ size_diff > ( optimization. original_size / 2.0 ) ,
827+ "The optimized size savings are too small: {}" ,
828+ size_diff,
792829 ) ;
793830
794831 Ok ( ( ) )
@@ -930,6 +967,7 @@ mod tests_ci_only {
930967 verbosity : VerbosityFlags :: default ( ) ,
931968 unstable_options : UnstableOptions :: default ( ) ,
932969 optimization_passes : None ,
970+ keep_debug_symbols : false ,
933971 } ;
934972 let res = cmd. exec ( ) . expect ( "build failed" ) ;
935973
@@ -944,4 +982,24 @@ mod tests_ci_only {
944982 Ok ( ( ) )
945983 } )
946984 }
985+
986+ #[ test]
987+ fn keep_debug_symbols ( ) {
988+ with_new_contract_project ( |manifest_path| {
989+ let res = super :: execute (
990+ & manifest_path,
991+ Verbosity :: Default ,
992+ BuildArtifacts :: CodeOnly ,
993+ UnstableFlags :: default ( ) ,
994+ OptimizationPasses :: default ( ) ,
995+ true ,
996+ )
997+ . expect ( "build failed" ) ;
998+
999+ // we specified that debug symbols should be kept
1000+ assert ! ( has_debug_symbols( & res. dest_wasm. unwrap( ) ) ) ;
1001+
1002+ Ok ( ( ) )
1003+ } )
1004+ }
9471005}
0 commit comments