diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 85ec744d0a63493c542f69e55595f26b34b505c5..6ec536f727a2f34b10dffb5d8d21652df3c652c0 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -29,8 +29,8 @@ use crate::{ crate_metadata::CrateMetadata, maybe_println, util, validate_wasm, workspace::{ManifestPath, Profile, Workspace}, - BuildArtifacts, BuildResult, OptimizationResult, UnstableFlags, UnstableOptions, Verbosity, - VerbosityFlags, + BuildArtifacts, BuildResult, OptimizationFlags, OptimizationPasses, OptimizationResult, + UnstableFlags, UnstableOptions, Verbosity, VerbosityFlags, }; use anyhow::{Context, Result}; use colored::Colorize; @@ -66,6 +66,8 @@ pub struct BuildCommand { verbosity: VerbosityFlags, #[structopt(flatten)] unstable_options: UnstableOptions, + #[structopt(flatten)] + optimization_passes: OptimizationFlags, } impl BuildCommand { @@ -74,12 +76,15 @@ impl BuildCommand { let unstable_flags: UnstableFlags = TryFrom::<&UnstableOptions>::try_from(&self.unstable_options)?; let verbosity = TryFrom::<&VerbosityFlags>::try_from(&self.verbosity)?; + let optimization_passes = + TryFrom::<&OptimizationFlags>::try_from(&self.optimization_passes)?; execute( &manifest_path, verbosity, true, self.build_artifact, unstable_flags, + optimization_passes, ) } } @@ -102,12 +107,14 @@ impl CheckCommand { let unstable_flags: UnstableFlags = TryFrom::<&UnstableOptions>::try_from(&self.unstable_options)?; let verbosity: Verbosity = TryFrom::<&VerbosityFlags>::try_from(&self.verbosity)?; + let optimization_passes = OptimizationPasses::Zero; execute( &manifest_path, verbosity, false, BuildArtifacts::CheckOnly, unstable_flags, + optimization_passes, ) } } @@ -268,14 +275,16 @@ fn post_process_wasm(crate_metadata: &CrateMetadata) -> Result<()> { /// /// The intention is to reduce the size of bloated wasm binaries as a result of missing /// optimizations (or bugs?) between Rust and Wasm. -fn optimize_wasm(crate_metadata: &CrateMetadata) -> Result { +fn optimize_wasm( + crate_metadata: &CrateMetadata, + optimization_passes: OptimizationPasses, +) -> Result { let mut dest_optimized = crate_metadata.dest_wasm.clone(); dest_optimized.set_file_name(format!("{}-opt.wasm", crate_metadata.package_name)); - let _ = do_optimization( crate_metadata.dest_wasm.as_os_str(), &dest_optimized.as_os_str(), - 3, + optimization_passes, )?; let original_size = metadata(&crate_metadata.dest_wasm)?.len() as f64 / 1000.0; @@ -299,17 +308,17 @@ fn optimize_wasm(crate_metadata: &CrateMetadata) -> Result { fn do_optimization( dest_wasm: &OsStr, dest_optimized: &OsStr, - optimization_level: u32, + optimization_level: OptimizationPasses, ) -> Result<()> { let mut dest_wasm_file = File::open(dest_wasm)?; let mut dest_wasm_file_content = Vec::new(); dest_wasm_file.read_to_end(&mut dest_wasm_file_content)?; let codegen_config = binaryen::CodegenConfig { - // number of optimization passes (spends potentially a lot of time optimizing) - optimization_level, + // Number of optimization passes (spends potentially a lot of time optimizing) + optimization_level: optimization_level.to_passes(), // the default - shrink_level: 1, + shrink_level: optimization_level.to_shrink(), // the default debug_info: false, }; @@ -334,7 +343,7 @@ fn do_optimization( fn do_optimization( dest_wasm: &OsStr, dest_optimized: &OsStr, - optimization_level: u32, + optimization_level: OptimizationPasses, ) -> Result<()> { // check `wasm-opt` is installed if which::which("wasm-opt").is_err() { @@ -349,7 +358,7 @@ fn do_optimization( let output = Command::new("wasm-opt") .arg(dest_wasm) - .arg(format!("-O{}", optimization_level)) + .arg(format!("-O{}", optimization_level.to_str())) .arg("-o") .arg(dest_optimized) // the memory in our module is imported, `wasm-opt` needs to be told that @@ -381,6 +390,7 @@ fn execute( optimize_contract: bool, build_artifact: BuildArtifacts, unstable_flags: UnstableFlags, + optimization_passes: OptimizationPasses, ) -> Result { if build_artifact == BuildArtifacts::CodeOnly || build_artifact == BuildArtifacts::CheckOnly { let crate_metadata = CrateMetadata::collect(manifest_path)?; @@ -390,6 +400,7 @@ fn execute( optimize_contract, build_artifact, unstable_flags, + optimization_passes, )?; let res = BuildResult { dest_wasm: maybe_dest_wasm, @@ -403,7 +414,13 @@ fn execute( return Ok(res); } - let res = super::metadata::execute(&manifest_path, verbosity, build_artifact, unstable_flags)?; + let res = super::metadata::execute( + &manifest_path, + verbosity, + build_artifact, + unstable_flags, + optimization_passes, + )?; Ok(res) } @@ -422,6 +439,7 @@ pub(crate) fn execute_with_crate_metadata( optimize_contract: bool, build_artifact: BuildArtifacts, unstable_flags: UnstableFlags, + optimization_passes: OptimizationPasses, ) -> Result<(Option, Option)> { maybe_println!( verbosity, @@ -446,7 +464,7 @@ pub(crate) fn execute_with_crate_metadata( format!("[3/{}]", build_artifact.steps()).bold(), "Optimizing wasm file".bright_green().bold() ); - let optimization_result = optimize_wasm(&crate_metadata)?; + let optimization_result = optimize_wasm(&crate_metadata, optimization_passes)?; Ok(( Some(crate_metadata.dest_wasm.clone()), Some(optimization_result), @@ -464,12 +482,14 @@ mod tests_ci_only { cmd::new::execute("new_project", Some(path)).expect("new project creation failed"); let manifest_path = ManifestPath::new(&path.join("new_project").join("Cargo.toml")).unwrap(); + let optimization_passes = 3; let res = super::execute( &manifest_path, None, true, BuildArtifacts::All, UnstableFlags::default(), + optimization_passes, ) .expect("build failed"); @@ -495,6 +515,7 @@ mod tests_ci_only { cmd::new::execute("new_project", Some(path)).expect("new project creation failed"); let project_dir = path.join("new_project"); let manifest_path = ManifestPath::new(&project_dir.join("Cargo.toml")).unwrap(); + let optimization_passes = 3; // when super::execute( @@ -503,6 +524,7 @@ mod tests_ci_only { true, BuildArtifacts::CheckOnly, UnstableFlags::default(), + optimization_passes, ) .expect("build failed"); diff --git a/src/cmd/metadata.rs b/src/cmd/metadata.rs index 387e16bc6571766643f016f5d39dcea2b90288f5..bb1e73a1a2e123f044d05091a92a4d3a3def7cc1 100644 --- a/src/cmd/metadata.rs +++ b/src/cmd/metadata.rs @@ -18,7 +18,7 @@ use crate::{ crate_metadata::CrateMetadata, maybe_println, util, workspace::{ManifestPath, Workspace}, - BuildArtifacts, BuildResult, OptimizationResult, UnstableFlags, Verbosity, + BuildArtifacts, BuildResult, OptimizationPasses, OptimizationResult, UnstableFlags, Verbosity, }; use anyhow::Result; @@ -40,6 +40,7 @@ struct GenerateMetadataCommand { verbosity: Verbosity, build_artifact: BuildArtifacts, unstable_options: UnstableFlags, + optimization_passes: OptimizationPasses, } /// Result of generating the extended contract project metadata @@ -239,6 +240,7 @@ impl GenerateMetadataCommand { true, // for the hash we always use the optimized version of the contract self.build_artifact, self.unstable_options.clone(), + self.optimization_passes, )?; let wasm = fs::read(&self.crate_metadata.dest_wasm)?; @@ -265,6 +267,7 @@ pub(crate) fn execute( verbosity: Verbosity, build_artifact: BuildArtifacts, unstable_options: UnstableFlags, + optimization_passes: OptimizationPasses, ) -> Result { let crate_metadata = CrateMetadata::collect(manifest_path)?; let res = GenerateMetadataCommand { @@ -272,6 +275,7 @@ pub(crate) fn execute( verbosity, build_artifact, unstable_options, + optimization_passes, } .exec()?; Ok(res) diff --git a/src/main.rs b/src/main.rs index 445d488645686d6f695f010d6fc21746dc43c33d..dccdb0b75daecba3060b73164dcbb38cb23fee54 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,6 +93,100 @@ impl ExtrinsicOpts { } } +#[derive(Clone, Debug, StructOpt)] +pub struct OptimizationFlags { + /// number of optimization passes, passed as an argument to wasm-opt + /// + /// - `0`: execute no optimization passes + /// + /// - `1`: execute 1 optimization pass (quick & useful opts, useful for iteration builds) + /// + /// - `2`, execute 2 optimization passes (most opts, generally gets most perf) + /// + /// - `3`, execute 3 optimization passes (spends potentially a lot of time optimizing) + /// + /// - `4`, execute 4 optimization passes (also flatten the IR, which can take a lot more time and memory + /// but is useful on more nested / complex / less-optimized input) + /// + /// - `s`, execute default optimization passes, focusing on code size + /// + /// - `z`, execute default optimization passes, super-focusing on code size + /// + /// - + #[structopt(long = "optimization-passes", default_value = "3")] + optimization_passes: String, +} + +#[derive(Clone, Copy)] +pub enum OptimizationPasses { + Zero, + One, + Two, + Three, + Four, + S, + Z, +} + +impl TryFrom<&OptimizationFlags> for OptimizationPasses { + type Error = Error; + + fn try_from(value: &OptimizationFlags) -> Result { + match value.optimization_passes.to_lowercase().as_str() { + "0" => Ok(OptimizationPasses::Zero), + "1" => Ok(OptimizationPasses::One), + "2" => Ok(OptimizationPasses::Two), + "3" => Ok(OptimizationPasses::Three), + "4" => Ok(OptimizationPasses::Four), + "s" => Ok(OptimizationPasses::S), + "z" => Ok(OptimizationPasses::Z), + _ => anyhow::bail!( + "Unknown optimization passes option {}", + value.optimization_passes + ), + } + } +} + +impl OptimizationPasses { + /// Returns the string representation of `OptimizationPasses` + pub(crate) fn to_str(&self) -> &str { + match self { + OptimizationPasses::Zero => "0", + OptimizationPasses::One => "1", + OptimizationPasses::Two => "2", + OptimizationPasses::Three => "3", + OptimizationPasses::Four => "4", + OptimizationPasses::S => "s", + OptimizationPasses::Z => "z", + } + } + + /// Returns the number of optimization passes to do + #[cfg(feature = "binaryen-as-dependency")] + pub(crate) fn to_passes(&self) -> u32 { + match self { + OptimizationPasses::Zero => 0, + OptimizationPasses::One => 1, + OptimizationPasses::Two => 2, + OptimizationPasses::Three => 3, + OptimizationPasses::Four => 4, + _ => 3, // Default to three for shrink settings + } + } + + /// Returns amount of shrinkage to do + #[cfg(feature = "binaryen-as-dependency")] + pub(crate) fn to_shrink(&self) -> u32 { + match self { + OptimizationPasses::Zero => 0, + OptimizationPasses::S => 1, + OptimizationPasses::Z => 2, + _ => 1, + } + } +} + #[derive(Clone, Debug, StructOpt)] pub struct VerbosityFlags { /// No output printed to stdout