gear_wasm_optimizer/
optimize.rs1#[cfg(not(feature = "wasm-opt"))]
20use colored::Colorize;
21#[cfg(not(feature = "wasm-opt"))]
22use std::process::Command;
23
24#[cfg(feature = "wasm-opt")]
25use wasm_opt::{OptimizationOptions, Pass};
26
27use crate::stack_end;
28use anyhow::{anyhow, Context, Result};
29use gear_wasm_instrument::{Module, STACK_END_EXPORT_NAME};
30use std::{
31 fs::{self, metadata},
32 path::{Path, PathBuf},
33};
34
35pub const FUNC_EXPORTS: [&str; 4] = ["init", "handle", "handle_reply", "handle_signal"];
36
37const OPTIMIZED_EXPORTS: [&str; 7] = [
38 "handle",
39 "handle_reply",
40 "handle_signal",
41 "init",
42 "state",
43 "metahash",
44 STACK_END_EXPORT_NAME,
45];
46
47pub struct Optimizer {
48 module: Module,
49}
50
51impl Optimizer {
52 pub fn new(file: &PathBuf) -> Result<Self> {
53 let contents = fs::read(file)
54 .with_context(|| format!("Failed to read file by optimizer: {file:?}"))?;
55 let module = Module::new(&contents).with_context(|| format!("File path: {file:?}"))?;
56 Ok(Self { module })
57 }
58
59 pub fn insert_start_call_in_export_funcs(&mut self) -> Result<(), &'static str> {
60 stack_end::insert_start_call_in_export_funcs(&mut self.module)
61 }
62
63 pub fn move_mut_globals_to_static(&mut self) -> Result<(), &'static str> {
64 stack_end::move_mut_globals_to_static(&mut self.module)
65 }
66
67 pub fn insert_stack_end_export(&mut self) -> Result<(), &'static str> {
68 stack_end::insert_stack_end_export(&mut self.module)
69 }
70
71 pub fn strip_custom_sections(&mut self) {
76 self.module.custom_section = None;
79 self.module.name_section = None;
80 }
81
82 pub fn strip_exports(&mut self) {
84 if let Some(export_section) = self.module.export_section.as_mut() {
85 let exports = OPTIMIZED_EXPORTS.map(str::to_string).to_vec();
86
87 export_section.retain(|export| exports.contains(&export.name.to_string()));
88 }
89 }
90
91 pub fn serialize(&self) -> Result<Vec<u8>> {
92 self.module
93 .serialize()
94 .context("Failed to serialize module")
95 }
96
97 pub fn flush_to_file(self, path: &PathBuf) {
98 fs::write(path, self.module.serialize().unwrap()).unwrap();
99 }
100}
101
102pub struct OptimizationResult {
103 pub original_size: f64,
104 pub optimized_size: f64,
105}
106
107pub fn optimize_wasm<P: AsRef<Path>>(
112 source: P,
113 destination: P,
114 optimization_passes: &str,
115 keep_debug_symbols: bool,
116) -> Result<OptimizationResult> {
117 let original_size = metadata(&source)?.len() as f64 / 1000.0;
118
119 do_optimization(
120 &source,
121 &destination,
122 optimization_passes,
123 keep_debug_symbols,
124 )?;
125
126 let destination = destination.as_ref();
127 if !destination.exists() {
128 return Err(anyhow!(
129 "Optimization failed, optimized wasm output file `{}` not found.",
130 destination.display()
131 ));
132 }
133
134 let optimized_size = metadata(destination)?.len() as f64 / 1000.0;
135
136 Ok(OptimizationResult {
137 original_size,
138 optimized_size,
139 })
140}
141
142#[cfg(not(feature = "wasm-opt"))]
143pub fn do_optimization<P: AsRef<Path>>(
151 dest_wasm: P,
152 dest_optimized: P,
153 optimization_level: &str,
154 keep_debug_symbols: bool,
155) -> Result<()> {
156 let which = which::which("wasm-opt");
158 if which.is_err() {
159 return Err(anyhow!(
160 "wasm-opt not found! Make sure the binary is in your PATH environment.\n\n\
161 We use this tool to optimize the size of your program's Wasm binary.\n\n\
162 wasm-opt is part of the binaryen package. You can find detailed\n\
163 installation instructions on https://github.com/WebAssembly/binaryen#tools.\n\n\
164 There are ready-to-install packages for many platforms:\n\
165 * Debian/Ubuntu: apt-get install binaryen\n\
166 * Homebrew: brew install binaryen\n\
167 * Arch Linux: pacman -S binaryen\n\
168 * Windows: binary releases at https://github.com/WebAssembly/binaryen/releases"
169 .bright_yellow()
170 ));
171 }
172 let wasm_opt_path = which
173 .as_ref()
174 .expect("we just checked if `which` returned an err; qed")
175 .as_path();
176 log::info!("Path to wasm-opt executable: {}", wasm_opt_path.display());
177
178 log::info!(
179 "Optimization level passed to wasm-opt: {}",
180 optimization_level
181 );
182 let mut command = Command::new(wasm_opt_path);
183 command
184 .arg(dest_wasm.as_ref())
185 .arg(format!("-O{optimization_level}"))
186 .arg("-o")
187 .arg(dest_optimized.as_ref())
188 .arg("-mvp")
189 .arg("--enable-sign-ext")
190 .arg("--enable-mutable-globals")
191 .arg("--zero-filled-memory")
195 .arg("--dae")
196 .arg("--vacuum");
197 if keep_debug_symbols {
198 command.arg("-g");
199 }
200 log::info!("Invoking wasm-opt with {:?}", command);
201 let output = command.output().unwrap();
202
203 if !output.status.success() {
204 let err = std::str::from_utf8(&output.stderr)
205 .expect("Cannot convert stderr output of wasm-opt to string")
206 .trim();
207 panic!(
208 "The wasm-opt optimization failed.\n\n\
209 The error which wasm-opt returned was: \n{err}"
210 );
211 }
212 Ok(())
213}
214
215#[cfg(feature = "wasm-opt")]
216pub fn do_optimization<P: AsRef<Path>>(
224 dest_wasm: P,
225 dest_optimized: P,
226 optimization_level: &str,
227 keep_debug_symbols: bool,
228) -> Result<()> {
229 log::info!(
230 "Optimization level passed to wasm-opt: {}",
231 optimization_level
232 );
233 match optimization_level {
234 "0" => OptimizationOptions::new_opt_level_0(),
235 "1" => OptimizationOptions::new_opt_level_1(),
236 "2" => OptimizationOptions::new_opt_level_2(),
237 "3" => OptimizationOptions::new_opt_level_3(),
238 "4" => OptimizationOptions::new_opt_level_4(),
239 "s" => OptimizationOptions::new_optimize_for_size(),
240 "z" => OptimizationOptions::new_optimize_for_size_aggressively(),
241 _ => panic!("Invalid optimization level {}", optimization_level),
242 }
243 .mvp_features_only()
244 .enable_feature(wasm_opt::Feature::SignExt)
245 .enable_feature(wasm_opt::Feature::MutableGlobals)
246 .shrink_level(wasm_opt::ShrinkLevel::Level2)
247 .add_pass(Pass::Dae)
248 .add_pass(Pass::Vacuum)
249 .zero_filled_memory(true)
253 .debug_info(keep_debug_symbols)
254 .run(dest_wasm, dest_optimized)?;
255
256 Ok(())
257}