wasm_opt/
run.rs

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/// An error resulting from the [`OptimizationOptions::run`] method.
12#[derive(Error, Debug)]
13pub enum OptimizationError {
14    /// The input module did not validate.
15    #[error("Failed to validate wasm: error validating input")]
16    ValidateWasmInput,
17    /// The output module did not validate.
18    #[error("Failed to validate wasm: error after opts")]
19    ValidateWasmOutput,
20    /// An error occurred while reading the input module.
21    #[error("Failed to read module")]
22    Read {
23        #[source]
24        source: Box<dyn std::error::Error + Send + Sync + 'static>,
25    },
26    /// An error occurred while writing the output module.
27    #[error("Failed to write module")]
28    Write {
29        #[source]
30        source: Box<dyn std::error::Error + Send + Sync + 'static>,
31    },
32    /// The input file path represents stdin to Binaryen,
33    /// but the API does not support reading stdin.
34    #[error("Refusing to read from stdin")]
35    InvalidStdinPath,
36}
37
38/// Execution.
39impl OptimizationOptions {
40    /// Run the Binaryen wasm optimizer.
41    ///
42    /// This loads a module from a file,
43    /// runs optimization passes,
44    /// and writes the module back to a file.
45    ///
46    /// To supply sourcemaps for the input module,
47    /// and preserve them for the output module,
48    /// use [`OptimizationOptions::run_with_sourcemaps`].
49    ///
50    /// # Errors
51    ///
52    /// Returns error on I/O failure, or if the input fails to parse.
53    /// If [`PassOptions::validate`] is true, it returns an error
54    /// if the input module fails to validate, or if the optimized
55    /// module fails to validate.
56    ///
57    /// The Rust API does not support reading a module on stdin, as the CLI
58    /// does. If `infile` is empty or "-",
59    /// [`OptimizationError::InvalidStdinPath`] is returned.
60    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    /// Run the Binaryen wasm optimizer.
69    ///
70    /// This loads a module from a file,
71    /// runs optimization passes,
72    /// and writes the module back to a file.
73    ///
74    /// The sourcemap arguments are optional, and only have effect
75    /// when reading or writing binary `wasm` files. When using
76    /// text `wat` files the respective sourcemap argument is ignored.
77    ///
78    /// # Errors
79    ///
80    /// Returns error on I/O failure, or if the input fails to parse.
81    /// If [`PassOptions::validate`] is true, it returns an error
82    /// if the input module fails to validate, or if the optimized
83    /// module fails to validate.
84    ///
85    /// The Rust API does not support reading a module on stdin, as the CLI
86    /// does. If `infile` is empty or "-",
87    /// [`OptimizationError::InvalidStdinPath`] is returned.
88    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        // Other features still disabled
384
385        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        // Other features still enabled
410
411        assert!(has(&enabled, BaseFeature::Atomics));
412        assert!(!has(&disabled, BaseFeature::Atomics));
413    }
414}