1use crate::api::*;
2use crate::base::{
3 validate_wasm, Feature as BaseFeature, FeatureSet as BaseFeatureSet,
4 InliningOptions as BaseInliningOptions, Module, ModuleReader, ModuleWriter,
5 PassOptions as BasePassOptions, PassRunner,
6};
7use std::fs;
8use std::path::Path;
9use thiserror::Error;
10
11#[derive(Error, Debug)]
13pub enum OptimizationError {
14 #[error("Failed to validate wasm: error validating input")]
16 ValidateWasmInput,
17 #[error("Failed to validate wasm: error after opts")]
19 ValidateWasmOutput,
20 #[error("Failed to read module")]
22 Read {
23 #[source]
24 source: Box<dyn std::error::Error + Send + Sync + 'static>,
25 },
26 #[error("Failed to write module")]
28 Write {
29 #[source]
30 source: Box<dyn std::error::Error + Send + Sync + 'static>,
31 },
32 #[error("Refusing to read from stdin")]
35 InvalidStdinPath,
36}
37
38impl OptimizationOptions {
40 pub fn run(
61 &self,
62 infile: impl AsRef<Path>,
63 outfile: impl AsRef<Path>,
64 ) -> Result<(), OptimizationError> {
65 self.run_with_sourcemaps(infile, None::<&str>, outfile, None::<&str>, None::<&str>)
66 }
67
68 pub fn run_with_sourcemaps(
89 &self,
90 infile: impl AsRef<Path>,
91 infile_sourcemap: Option<impl AsRef<Path>>,
92 outfile: impl AsRef<Path>,
93 outfile_sourcemap: Option<impl AsRef<Path>>,
94 sourcemap_url: Option<impl AsRef<str>>,
95 ) -> Result<(), OptimizationError> {
96 let infile: &Path = infile.as_ref();
97 let infile_sourcemap: Option<&Path> = infile_sourcemap.as_ref().map(AsRef::as_ref);
98 let outfile: &Path = outfile.as_ref();
99 let outfile_sourcemap: Option<&Path> = outfile_sourcemap.as_ref().map(AsRef::as_ref);
100 let sourcemap_url: Option<&str> = sourcemap_url.as_ref().map(AsRef::as_ref);
101
102 if infile.as_os_str().is_empty() || infile == Path::new("-") {
103 return Err(OptimizationError::InvalidStdinPath);
104 }
105
106 let mut m = Module::new();
107 self.apply_features(&mut m);
108
109 {
110 let mut reader = ModuleReader::new();
111
112 let set_dwarf =
113 self.passopts.debug_info && !will_remove_debug_info(&self.passes.more_passes);
114 reader.set_dwarf(set_dwarf);
115
116 match self.reader.file_type {
117 FileType::Wasm => reader.read_binary(infile, &mut m, infile_sourcemap),
118 FileType::Wat => reader.read_text(infile, &mut m),
119 FileType::Any => reader.read(infile, &mut m, infile_sourcemap),
120 }
121 .map_err(|e| OptimizationError::Read {
122 source: Box::from(e),
123 })?;
124 }
125
126 {
127 if self.passopts.validate && !validate_wasm(&mut m) {
128 return Err(OptimizationError::ValidateWasmInput);
129 }
130
131 self.create_and_run_pass_runner(&mut m);
132
133 if self.converge {
134 self.run_until_convergence(&mut m)
135 .map_err(|e| OptimizationError::Write {
136 source: Box::from(e),
137 })?;
138 }
139
140 if self.passopts.validate && !validate_wasm(&mut m) {
141 return Err(OptimizationError::ValidateWasmOutput);
142 }
143 }
144
145 {
146 let mut writer = ModuleWriter::new();
147 writer.set_debug_info(self.passopts.debug_info);
148
149 if let Some(filename) = outfile_sourcemap {
150 writer
151 .set_source_map_filename(filename)
152 .map_err(|e| OptimizationError::Write {
153 source: Box::from(e),
154 })?;
155 }
156
157 if let Some(url) = sourcemap_url {
158 writer.set_source_map_url(url);
159 }
160
161 match self.writer.file_type {
162 FileType::Wasm => writer.write_binary(&mut m, outfile),
163 FileType::Wat => writer.write_text(&mut m, outfile),
164 FileType::Any => match self.reader.file_type {
165 FileType::Any | FileType::Wasm => writer.write_binary(&mut m, outfile),
166 FileType::Wat => writer.write_text(&mut m, outfile),
167 },
168 }
169 .map_err(|e| OptimizationError::Write {
170 source: Box::from(e),
171 })?;
172 }
173
174 Ok(())
175 }
176
177 fn create_and_run_pass_runner(&self, m: &mut Module) {
178 let passopts = self.translate_pass_options();
179
180 let mut pass_runner = PassRunner::new_with_options(m, passopts);
181
182 if self.passes.add_default_passes {
183 pass_runner.add_default_optimization_passes();
184 }
185
186 self.passes
187 .more_passes
188 .iter()
189 .for_each(|pass| pass_runner.add(pass.name()));
190
191 pass_runner.run();
192 }
193
194 fn run_until_convergence(&self, m: &mut Module) -> anyhow::Result<()> {
195 let mut last_size = Self::get_module_size(m)?;
196 let mut current_size;
197
198 loop {
199 self.create_and_run_pass_runner(m);
200
201 current_size = Self::get_module_size(m)?;
202
203 if current_size >= last_size {
204 break;
205 }
206
207 last_size = current_size;
208 }
209
210 Ok(())
211 }
212
213 fn get_module_size(m: &mut Module) -> anyhow::Result<usize> {
214 let tempdir = tempfile::tempdir()?;
215 let temp_outfile = tempdir.path().join("wasm_opt_temp_outfile.wasm");
216
217 let mut writer = ModuleWriter::new();
218 writer.write_binary(m, &temp_outfile)?;
219
220 let file_size = fs::read(&temp_outfile)?.len();
221
222 Ok(file_size)
223 }
224
225 fn apply_features(&self, m: &mut Module) {
226 let (enabled_features, disabled_features) = convert_feature_sets(&self.features);
227
228 m.apply_features(enabled_features, disabled_features);
229 }
230
231 fn translate_pass_options(&self) -> BasePassOptions {
232 let mut opts = BasePassOptions::new();
233
234 opts.set_validate(self.passopts.validate);
235 opts.set_validate_globally(self.passopts.validate_globally);
236 opts.set_optimize_level(self.passopts.optimize_level as i32);
237 opts.set_shrink_level(self.passopts.shrink_level as i32);
238 opts.set_traps_never_happen(self.passopts.traps_never_happen);
239 opts.set_low_memory_unused(self.passopts.low_memory_unused);
240 opts.set_fast_math(self.passopts.fast_math);
241 opts.set_zero_filled_memory(self.passopts.zero_filled_memory);
242 opts.set_debug_info(self.passopts.debug_info);
243
244 self.passopts
245 .arguments
246 .iter()
247 .for_each(|(key, value)| opts.set_arguments(key, value));
248
249 let mut inlining = BaseInliningOptions::new();
250 inlining.set_always_inline_max_size(self.inlining.always_inline_max_size);
251 inlining.set_one_caller_inline_max_size(self.inlining.one_caller_inline_max_size);
252 inlining.set_flexible_inline_max_size(self.inlining.flexible_inline_max_size);
253 inlining.set_allow_functions_with_loops(self.inlining.allow_functions_with_loops);
254 inlining.set_partial_inlining_ifs(self.inlining.partial_inlining_ifs);
255
256 opts.set_inlining_options(inlining);
257
258 opts
259 }
260}
261
262fn will_remove_debug_info(passes: &[Pass]) -> bool {
263 passes
264 .iter()
265 .any(|pass| PassRunner::pass_removes_debug_info(pass.name()) == true)
266}
267
268fn convert_feature_sets(features: &Features) -> (BaseFeatureSet, BaseFeatureSet) {
269 let mut feature_set_enabled = BaseFeatureSet::new();
270 let mut feature_set_disabled = BaseFeatureSet::new();
271
272 match features.baseline {
273 FeatureBaseline::Default => {
274 feature_set_enabled.set(BaseFeature::Default, true);
275 }
276 FeatureBaseline::MvpOnly => {
277 feature_set_enabled.set_mvp();
278 feature_set_disabled.set_all();
279 }
280 FeatureBaseline::All => {
281 feature_set_enabled.set_all();
282 feature_set_disabled.set_mvp();
283 }
284 }
285
286 features.enabled.iter().for_each(|f| {
287 let feature = convert_feature(f);
288 feature_set_enabled.set(feature, true);
289 feature_set_disabled.set(feature, false);
290 });
291
292 features.disabled.iter().for_each(|f| {
293 let feature = convert_feature(f);
294 feature_set_enabled.set(feature, false);
295 feature_set_disabled.set(feature, true);
296 });
297
298 (feature_set_enabled, feature_set_disabled)
299}
300
301fn convert_feature(feature: &Feature) -> BaseFeature {
302 match feature {
303 Feature::None => BaseFeature::None,
304 Feature::Atomics => BaseFeature::Atomics,
305 Feature::MutableGlobals => BaseFeature::MutableGlobals,
306 Feature::TruncSat => BaseFeature::TruncSat,
307 Feature::Simd => BaseFeature::Simd,
308 Feature::BulkMemory => BaseFeature::BulkMemory,
309 Feature::SignExt => BaseFeature::SignExt,
310 Feature::ExceptionHandling => BaseFeature::ExceptionHandling,
311 Feature::TailCall => BaseFeature::TailCall,
312 Feature::ReferenceTypes => BaseFeature::ReferenceTypes,
313 Feature::Multivalue => BaseFeature::Multivalue,
314 Feature::Gc => BaseFeature::Gc,
315 Feature::Memory64 => BaseFeature::Memory64,
316 Feature::RelaxedSimd => BaseFeature::RelaxedSimd,
317 Feature::ExtendedConst => BaseFeature::ExtendedConst,
318 Feature::Strings => BaseFeature::Strings,
319 Feature::MultiMemory => BaseFeature::MultiMemory,
320 Feature::Mvp => BaseFeature::None,
321 Feature::Default => BaseFeature::Default,
322 Feature::All => BaseFeature::All,
323 }
324}
325
326#[cfg(test)]
327mod test {
328 use super::*;
329
330 fn has(set: &BaseFeatureSet, feature: BaseFeature) -> bool {
331 let mut new_set = BaseFeatureSet::new();
332 new_set.set(feature, true);
333 set.has(&new_set)
334 }
335
336 #[test]
337 fn test_features_default() {
338 let features = Features::default();
339 let (enabled, disabled) = convert_feature_sets(&features);
340
341 assert_eq!(enabled.as_int(), BaseFeature::Default as u32);
342 assert_eq!(disabled.as_int(), BaseFeature::None as u32);
343
344 assert!(has(&enabled, BaseFeature::SignExt));
345 assert!(!has(&disabled, BaseFeature::SignExt));
346 assert!(has(&enabled, BaseFeature::MutableGlobals));
347 assert!(!has(&disabled, BaseFeature::MutableGlobals));
348 }
349
350 #[test]
351 fn test_features_remove_defaults() {
352 let mut opts = OptimizationOptions::new_optimize_for_size();
353 opts.disable_feature(Feature::SignExt)
354 .disable_feature(Feature::MutableGlobals);
355 let (enabled, disabled) = convert_feature_sets(&opts.features);
356
357 assert!(!has(&enabled, BaseFeature::SignExt));
358 assert!(has(&disabled, BaseFeature::SignExt));
359 assert!(!has(&enabled, BaseFeature::MutableGlobals));
360 assert!(has(&disabled, BaseFeature::MutableGlobals));
361 }
362
363 #[test]
364 fn test_features_mvp_and_enable() {
365 let mut opts = OptimizationOptions::new_optimize_for_size();
366 opts.mvp_features_only();
367
368 let (enabled, disabled) = convert_feature_sets(&opts.features);
369
370 assert!(has(&enabled, BaseFeature::None));
371 assert!(has(&disabled, BaseFeature::All));
372
373 assert!(!has(&enabled, BaseFeature::Gc));
374 assert!(has(&disabled, BaseFeature::Gc));
375
376 opts.enable_feature(Feature::Gc);
377
378 let (enabled, disabled) = convert_feature_sets(&opts.features);
379
380 assert!(has(&enabled, BaseFeature::Gc));
381 assert!(!has(&disabled, BaseFeature::Gc));
382
383 assert!(!has(&enabled, BaseFeature::Atomics));
386 assert!(has(&disabled, BaseFeature::Atomics));
387 }
388
389 #[test]
390 fn test_features_all_and_disable() {
391 let mut opts = OptimizationOptions::new_optimize_for_size();
392 opts.all_features();
393
394 let (enabled, disabled) = convert_feature_sets(&opts.features);
395
396 assert!(has(&enabled, BaseFeature::All));
397 assert!(has(&disabled, BaseFeature::None));
398
399 assert!(has(&enabled, BaseFeature::Gc));
400 assert!(!has(&disabled, BaseFeature::Gc));
401
402 opts.disable_feature(Feature::Gc);
403
404 let (enabled, disabled) = convert_feature_sets(&opts.features);
405
406 assert!(!has(&enabled, BaseFeature::Gc));
407 assert!(has(&disabled, BaseFeature::Gc));
408
409 assert!(has(&enabled, BaseFeature::Atomics));
412 assert!(!has(&disabled, BaseFeature::Atomics));
413 }
414}