gear_wasm_optimizer/
optimize.rs1use colored::Colorize;
20
21use crate::stack_end;
22use anyhow::{Context, Result, anyhow};
23use gear_wasm_instrument::{Module, STACK_END_EXPORT_NAME};
24use std::{
25 fs::{self, metadata},
26 path::{Path, PathBuf},
27 process::Command,
28};
29
30pub const FUNC_EXPORTS: [&str; 4] = ["init", "handle", "handle_reply", "handle_signal"];
31
32const OPTIMIZED_EXPORTS: [&str; 7] = [
33 "handle",
34 "handle_reply",
35 "handle_signal",
36 "init",
37 "state",
38 "metahash",
39 STACK_END_EXPORT_NAME,
40];
41
42pub struct Optimizer {
43 module: Module,
44}
45
46impl Optimizer {
47 pub fn new(file: &PathBuf) -> Result<Self> {
48 let contents = fs::read(file)
49 .with_context(|| format!("Failed to read file by optimizer: {file:?}"))?;
50 let module = Module::new(&contents).with_context(|| format!("File path: {file:?}"))?;
51 Ok(Self { module })
52 }
53
54 pub fn insert_start_call_in_export_funcs(&mut self) -> Result<(), &'static str> {
55 stack_end::insert_start_call_in_export_funcs(&mut self.module)
56 }
57
58 pub fn move_mut_globals_to_static(&mut self) -> Result<(), &'static str> {
59 stack_end::move_mut_globals_to_static(&mut self.module)
60 }
61
62 pub fn insert_stack_end_export(&mut self) -> Result<(), &'static str> {
63 stack_end::insert_stack_end_export(&mut self.module)
64 }
65
66 pub fn strip_custom_sections(&mut self) {
71 self.module.custom_sections = None;
74 self.module.name_section = None;
75 }
76
77 pub fn strip_exports(&mut self) {
79 if let Some(export_section) = self.module.export_section.as_mut() {
80 let exports = OPTIMIZED_EXPORTS.map(str::to_string).to_vec();
81
82 export_section.retain(|export| exports.contains(&export.name.to_string()));
83 }
84 }
85
86 pub fn serialize(&self) -> Result<Vec<u8>> {
87 self.module
88 .serialize()
89 .context("Failed to serialize module")
90 }
91
92 pub fn flush_to_file(self, path: &PathBuf) {
93 fs::write(path, self.module.serialize().unwrap()).unwrap();
94 }
95}
96
97pub struct OptimizationResult {
98 pub original_size: f64,
99 pub optimized_size: f64,
100}
101
102pub fn optimize_wasm<P: AsRef<Path>>(
107 source: P,
108 destination: P,
109 optimization_passes: &str,
110 keep_debug_symbols: bool,
111) -> Result<OptimizationResult> {
112 let original_size = metadata(&source)?.len() as f64 / 1000.0;
113
114 do_optimization(
115 &source,
116 &destination,
117 optimization_passes,
118 keep_debug_symbols,
119 )?;
120
121 let destination = destination.as_ref();
122 if !destination.exists() {
123 return Err(anyhow!(
124 "Optimization failed, optimized wasm output file `{}` not found.",
125 destination.display()
126 ));
127 }
128
129 let optimized_size = metadata(destination)?.len() as f64 / 1000.0;
130
131 Ok(OptimizationResult {
132 original_size,
133 optimized_size,
134 })
135}
136
137pub fn do_optimization<P: AsRef<Path>>(
145 dest_wasm: P,
146 dest_optimized: P,
147 optimization_level: &str,
148 keep_debug_symbols: bool,
149) -> Result<()> {
150 let which = which::which("wasm-opt");
152 if which.is_err() {
153 return Err(anyhow!(
154 "wasm-opt not found! Make sure the binary is in your PATH environment.\n\n\
155 We use this tool to optimize the size of your program's Wasm binary.\n\n\
156 wasm-opt is part of the binaryen package. You can find detailed\n\
157 installation instructions on https://github.com/WebAssembly/binaryen#tools.\n\n\
158 There are ready-to-install packages for many platforms:\n\
159 * Debian/Ubuntu: apt-get install binaryen\n\
160 * Homebrew: brew install binaryen\n\
161 * Arch Linux: pacman -S binaryen\n\
162 * Windows: binary releases at https://github.com/WebAssembly/binaryen/releases"
163 .bright_yellow()
164 ));
165 }
166 let wasm_opt_path = which
167 .as_ref()
168 .expect("we just checked if `which` returned an err; qed")
169 .as_path();
170 log::info!("Path to wasm-opt executable: {}", wasm_opt_path.display());
171
172 log::info!("Optimization level passed to wasm-opt: {optimization_level}");
173 let mut command = Command::new(wasm_opt_path);
174 command
175 .arg(dest_wasm.as_ref())
176 .arg(format!("-O{optimization_level}"))
177 .arg("-o")
178 .arg(dest_optimized.as_ref())
179 .arg("-mvp")
180 .arg("--enable-sign-ext")
181 .arg("--enable-mutable-globals")
182 .arg("--limit-segments")
183 .arg("--pass-arg=limit-segments@1024")
184 .arg("--zero-filled-memory")
188 .arg("--dae")
189 .arg("--vacuum");
190 if keep_debug_symbols {
191 command.arg("-g");
192 }
193 log::info!("Invoking wasm-opt with {command:?}");
194 let output = command.output().unwrap();
195
196 if !output.status.success() {
197 let err = std::str::from_utf8(&output.stderr)
198 .expect("Cannot convert stderr output of wasm-opt to string")
199 .trim();
200 panic!(
201 "The wasm-opt optimization failed.\n\n\
202 The error which wasm-opt returned was: \n{err}"
203 );
204 }
205 Ok(())
206}