contract_build/
wasm_opt.rs

1// Copyright (C) Use Ink (UK) Ltd.
2// This file is part of cargo-contract.
3//
4// cargo-contract is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// cargo-contract is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with cargo-contract.  If not, see <http://www.gnu.org/licenses/>.
16
17use anyhow::Result;
18use wasm_opt::{
19    Feature,
20    OptimizationOptions,
21    Pass,
22};
23
24use std::{
25    fmt,
26    path::PathBuf,
27    str,
28};
29
30/// A helpful struct for interacting with Binaryen's `wasm-opt` tool.
31pub struct WasmOptHandler {
32    /// The optimization level that should be used when optimizing the Wasm binary.
33    optimization_level: OptimizationPasses,
34    /// Whether or not to keep debugging information in the final Wasm binary.
35    keep_debug_symbols: bool,
36}
37
38impl WasmOptHandler {
39    /// Generate a new instance of the handler.
40    ///
41    /// Fails if the `wasm-opt` binary is not installed on the system, or if an outdated
42    /// `wasm-opt` binary is used (currently a version >= 99 is required).
43    pub fn new(
44        optimization_level: OptimizationPasses,
45        keep_debug_symbols: bool,
46    ) -> Result<Self> {
47        Ok(Self {
48            optimization_level,
49            keep_debug_symbols,
50        })
51    }
52
53    /// Attempts to perform optional Wasm optimization using Binaryen's `wasm-opt` tool.
54    ///
55    /// If successful, the optimized Wasm binary is written to `dest_wasm`.
56    pub fn optimize(&self, original_wasm: &PathBuf, dest_wasm: &PathBuf) -> Result<()> {
57        tracing::debug!(
58            "Optimization level passed to wasm-opt: {}",
59            self.optimization_level
60        );
61
62        OptimizationOptions::from(self.optimization_level)
63            .mvp_features_only()
64            // Since rustc 1.70 `SignExt` can't be disabled anymore. Hence we have to allow it,
65            // in order that the Wasm binary containing these instructions can be loaded.
66            .enable_feature(Feature::SignExt)
67            // This pass will then remove any `signext` instructions in order that the resulting
68            // Wasm binary is compatible with older versions of `pallet-contracts` which do not
69            // support the `signext` instruction.
70            .add_pass(Pass::SignextLowering)
71            // the memory in our module is imported, `wasm-opt` needs to be told that
72            // the memory is initialized to zeroes, otherwise it won't run the
73            // memory-packing pre-pass.
74            .zero_filled_memory(true)
75            .debug_info(self.keep_debug_symbols)
76            .run(original_wasm, dest_wasm)?;
77
78        if !dest_wasm.exists() {
79            return Err(anyhow::anyhow!(
80                "Optimization failed, optimized wasm output file `{}` not found.",
81                dest_wasm.display()
82            ))
83        }
84
85        Ok(())
86    }
87}
88
89#[derive(
90    Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize,
91)]
92pub enum OptimizationPasses {
93    Zero,
94    One,
95    Two,
96    Three,
97    Four,
98    S,
99    #[default]
100    Z,
101}
102
103impl fmt::Display for OptimizationPasses {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        let out = match self {
106            OptimizationPasses::Zero => "0",
107            OptimizationPasses::One => "1",
108            OptimizationPasses::Two => "2",
109            OptimizationPasses::Three => "3",
110            OptimizationPasses::Four => "4",
111            OptimizationPasses::S => "s",
112            OptimizationPasses::Z => "z",
113        };
114        write!(f, "{out}")
115    }
116}
117
118impl str::FromStr for OptimizationPasses {
119    type Err = anyhow::Error;
120
121    fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
122        // We need to replace " here, since the input string could come
123        // from either the CLI or the `Cargo.toml` profile section.
124        // If it is from the profile it could e.g. be "3" or 3.
125        let normalized_input = input.replace('"', "").to_lowercase();
126        match normalized_input.as_str() {
127            "0" => Ok(OptimizationPasses::Zero),
128            "1" => Ok(OptimizationPasses::One),
129            "2" => Ok(OptimizationPasses::Two),
130            "3" => Ok(OptimizationPasses::Three),
131            "4" => Ok(OptimizationPasses::Four),
132            "s" => Ok(OptimizationPasses::S),
133            "z" => Ok(OptimizationPasses::Z),
134            _ => anyhow::bail!("Unknown optimization passes for option {}", input),
135        }
136    }
137}
138
139impl From<String> for OptimizationPasses {
140    fn from(str: String) -> Self {
141        <OptimizationPasses as str::FromStr>::from_str(&str).expect("conversion failed")
142    }
143}
144
145impl From<OptimizationPasses> for OptimizationOptions {
146    fn from(passes: OptimizationPasses) -> OptimizationOptions {
147        match passes {
148            OptimizationPasses::Zero => OptimizationOptions::new_opt_level_0(),
149            OptimizationPasses::One => OptimizationOptions::new_opt_level_1(),
150            OptimizationPasses::Two => OptimizationOptions::new_opt_level_2(),
151            OptimizationPasses::Three => OptimizationOptions::new_opt_level_3(),
152            OptimizationPasses::Four => OptimizationOptions::new_opt_level_4(),
153            OptimizationPasses::S => OptimizationOptions::new_optimize_for_size(),
154            OptimizationPasses::Z => {
155                OptimizationOptions::new_optimize_for_size_aggressively()
156            }
157        }
158    }
159}
160
161/// Result of the optimization process.
162#[derive(serde::Serialize, serde::Deserialize)]
163pub struct OptimizationResult {
164    /// The original Wasm size.
165    pub original_size: f64,
166    /// The Wasm size after optimizations have been applied.
167    pub optimized_size: f64,
168}