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        optimize(false, self.wasm.clone(), self.wasm_out.clone())
46    }
47}
48
49#[cfg(feature = "additional-libs")]
50pub fn optimize(
51    quiet: bool,
52    wasm: Vec<PathBuf>,
53    wasm_out: Option<std::path::PathBuf>,
54) -> Result<(), Error> {
55    if wasm.len() > 1 && wasm_out.is_some() {
56        return Err(Error::MultipleFilesOutput);
57    }
58
59    for wasm_path in &wasm {
60        let wasm_arg = wasm::Args {
61            wasm: wasm_path.into(),
62        };
63
64        if !quiet {
65            println!(
66                "Reading: {path} ({wasm_size} bytes)",
67                path = wasm_arg.wasm.to_string_lossy(),
68                wasm_size = wasm_arg.len()?
69            );
70        }
71
72        let wasm_out = wasm_out.clone().unwrap_or_else(|| {
73            let mut wasm_out = wasm_arg.wasm.clone();
74            wasm_out.set_extension("optimized.wasm");
75            wasm_out
76        });
77
78        let mut options = OptimizationOptions::new_optimize_for_size_aggressively();
79        options.converge = true;
80
81        // Explicitly set to MVP + sign-ext + mutable-globals, which happens to
82        // also be the default featureset, but just to be extra clear we set it
83        // explicitly.
84        //
85        // Formerly Soroban supported only the MVP feature set, but Rust 1.70 as
86        // well as Clang generate code with sign-ext + mutable-globals enabled,
87        // so Soroban has taken a change to support them also.
88        options.mvp_features_only();
89        options.enable_feature(Feature::MutableGlobals);
90        options.enable_feature(Feature::SignExt);
91
92        options
93            .run(&wasm_arg.wasm, &wasm_out)
94            .map_err(Error::OptimizationError)?;
95
96        if !quiet {
97            println!(
98                "Optimized: {path} ({size} bytes)",
99                path = wasm_out.to_string_lossy(),
100                size = wasm::len(&wasm_out)?
101            );
102        }
103    }
104
105    Ok(())
106}