Skip to content

Commit 9b36b62

Browse files
atheiMichael Müllerascjones
authored
Add --keep-symbols flag (#302)
* Add `--keep-symbols` flag * Replace pwasm_utils::optimize by a simple export stripper * Satisfy clippy * Fix typos Co-authored-by: Michael Müller <michi@parity.io> * Fix test build errors * Fix tests * Rename to `--keep-debug-symbols` * Add test for `--keep-debug-symbols` * Fix typos Co-authored-by: Andrew Jones <ascjones@gmail.com> Co-authored-by: Michael Müller <michi@parity.io> * Restore when/then Co-authored-by: Michael Müller <michi@parity.io> Co-authored-by: Andrew Jones <ascjones@gmail.com>
1 parent d79d07b commit 9b36b62

File tree

4 files changed

+108
-62
lines changed

4 files changed

+108
-62
lines changed

Cargo.lock

Lines changed: 0 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ structopt = "0.3.22"
2525
log = "0.4.14"
2626
heck = "0.3.3"
2727
zip = { version = "0.5.13", default-features = false }
28-
pwasm-utils = "0.18.1"
2928
parity-wasm = "0.42.2"
3029
cargo_metadata = "0.14.0"
3130
codec = { package = "parity-scale-codec", version = "2.1", features = ["derive"] }

src/cmd/build.rs

Lines changed: 107 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use crate::{
2323
};
2424
use anyhow::{Context, Result};
2525
use colored::Colorize;
26-
use parity_wasm::elements::{External, MemoryType, Module, Section};
26+
use parity_wasm::elements::{External, Internal, MemoryType, Module, Section};
2727
use regex::Regex;
2828
use 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

9398
impl 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`.
263271
fn 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.
273301
fn 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<()> {
306325
fn 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(
600627
mod 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
}

src/cmd/metadata.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ mod tests {
328328
BuildArtifacts::All,
329329
UnstableFlags::default(),
330330
OptimizationPasses::default(),
331+
false,
331332
)?;
332333
let dest_bundle = build_result
333334
.metadata_result

0 commit comments

Comments
 (0)