soroban_cli/commands/contract/
optimize.rs

1use clap::{arg, Parser};
2use std::{fmt::Debug, path::PathBuf};
3#[cfg(feature = "additional-libs")]
4use wasm_opt::{Feature, OptimizationError, OptimizationOptions};
5
6use crate::wasm;
7
8#[derive(Parser, Debug, Clone)]
9#[group(skip)]
10pub struct Cmd {
11    /// Path to one or more wasm binaries
12    #[arg(long, num_args = 1.., required = true)]
13    wasm: Vec<PathBuf>,
14
15    /// Path to write the optimized WASM file to (defaults to same location as --wasm with .optimized.wasm suffix)
16    #[arg(long)]
17    wasm_out: Option<std::path::PathBuf>,
18}
19
20#[derive(thiserror::Error, Debug)]
21pub enum Error {
22    #[error(transparent)]
23    Wasm(#[from] wasm::Error),
24
25    #[cfg(feature = "additional-libs")]
26    #[error("optimization error: {0}")]
27    OptimizationError(OptimizationError),
28
29    #[cfg(not(feature = "additional-libs"))]
30    #[error("must install with \"additional-libs\" feature.")]
31    Install,
32
33    #[error("--wasm-out cannot be used with --wasm option when passing multiple files")]
34    MultipleFilesOutput,
35}
36
37impl Cmd {
38    #[cfg(not(feature = "additional-libs"))]
39    pub fn run(&self) -> Result<(), Error> {
40        Err(Error::Install)
41    }
42
43    #[cfg(feature = "additional-libs")]
44    pub fn run(&self) -> Result<(), Error> {
45        if self.wasm.len() > 1 && self.wasm_out.is_some() {
46            return Err(Error::MultipleFilesOutput);
47        }
48
49        for wasm_path in &self.wasm {
50            let wasm_arg = wasm::Args {
51                wasm: wasm_path.into(),
52            };
53            let wasm_size = wasm_arg.len()?;
54
55            println!(
56                "Reading: {} ({} bytes)",
57                wasm_arg.wasm.to_string_lossy(),
58                wasm_size
59            );
60
61            let wasm_out = self.wasm_out.clone().unwrap_or_else(|| {
62                let mut wasm_out = wasm_arg.wasm.clone();
63                wasm_out.set_extension("optimized.wasm");
64                wasm_out
65            });
66
67            let mut options = OptimizationOptions::new_optimize_for_size_aggressively();
68            options.converge = true;
69
70            // Explicitly set to MVP + sign-ext + mutable-globals, which happens to
71            // also be the default featureset, but just to be extra clear we set it
72            // explicitly.
73            //
74            // Formerly Soroban supported only the MVP feature set, but Rust 1.70 as
75            // well as Clang generate code with sign-ext + mutable-globals enabled,
76            // so Soroban has taken a change to support them also.
77            options.mvp_features_only();
78            options.enable_feature(Feature::MutableGlobals);
79            options.enable_feature(Feature::SignExt);
80
81            options
82                .run(&wasm_arg.wasm, &wasm_out)
83                .map_err(Error::OptimizationError)?;
84
85            let wasm_out_size = wasm::len(&wasm_out)?;
86            println!(
87                "Optimized: {} ({} bytes)",
88                wasm_out.to_string_lossy(),
89                wasm_out_size
90            );
91        }
92
93        Ok(())
94    }
95}