1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
pub use crate::features::Feature;
pub use crate::passes::Pass;
use crate::profiles::Profile;
use std::collections::{HashMap, HashSet};

/// Optimization options and optimization builder.
///
/// This type declares all supported Binaryen options.
/// It can be modified directly or by its [builder-pattern] methods.
///
/// Call [`OptimizationOptions::run`] to perform the optimizations.
///
/// [builder-pattern]: https://rust-unofficial.github.io/patterns/patterns/creational/builder.html
#[derive(Clone, Debug)]
pub struct OptimizationOptions {
    /// Options for reading the unoptimized wasm module.
    pub reader: ReaderOptions,
    /// Options for writing the optimized wasm module.
    pub writer: WriterOptions,
    /// Options related to inlining.
    pub inlining: InliningOptions,
    /// Options that affect how optimization passes behave.
    pub passopts: PassOptions,
    /// The set of optimization passes to apply.
    pub passes: Passes,
    /// The set of wasm-features.
    pub features: Features,
    /// Run passes to convergence, continuing while binary size decreases.
    pub converge: bool,
}

/// Options for reading the unoptimized wasm module.
#[derive(Copy, Clone, Debug)]
pub struct ReaderOptions {
    /// The module format: wasm, wat, or either.
    ///
    /// The default value is [`FileType::Any`].
    ///
    /// When this is `FileType::Any` the first bytes
    /// of the module will be inspected to determine whether
    /// the module is in binary or text format,
    /// then read as appropriate.
    pub file_type: FileType,
}

/// Options for writing the optimized wasm module.
#[derive(Clone, Debug)]
pub struct WriterOptions {
    /// The module format: wasm, wat, or either.
    ///
    /// The default value is [`FileType::Wasm`].
    ///
    /// Note that when this is [`FileType::Any`] the following logic applies:
    ///
    /// If [`ReaderOptions::file_type`] is [`FileType::Wat`],
    /// write a wat file, otherwise write a wasm file.
    pub file_type: FileType,
}

/// Module format used by [`ReaderOptions`] and [`WriterOptions`].
#[derive(Copy, Clone, Debug)]
pub enum FileType {
    /// A binary wasm module.
    Wasm,
    /// A text wasm module in wat format.
    Wat,
    /// Either a binary or text module.
    ///
    /// See the documentation for [`ReaderOptions`] and [`WriterOptions`]
    /// for an explanation of how this is interpreted.
    Any,
}

/// Options related to inlining.
#[derive(Copy, Clone, Debug)]
pub struct InliningOptions {
    /// Function size at which we always inline.
    ///
    /// Default: `2`.
    pub always_inline_max_size: u32,
    /// Function size which we inline when there is only one caller.
    ///
    /// Default: `u32::MAX`.
    pub one_caller_inline_max_size: u32,
    /// Function size above which we generally never inline.
    ///
    /// Default: `20`.
    pub flexible_inline_max_size: u32,
    /// Functions with loops are usually not inlined.
    ///
    /// Default: `false`.
    pub allow_functions_with_loops: bool,
    /// The number of `if`s to allow partial inlining of their conditions.
    ///
    /// Default: `0`.
    pub partial_inlining_ifs: u32,
}

/// Options that affect how optimization passes behave.
///
/// The Binaryen source code has more extensive documentation of these options
/// than is reproduced here.
#[derive(Clone, Debug)]
pub struct PassOptions {
    /// Validate both the unoptimized module and the optimized module.
    ///
    /// Default: `true`.
    pub validate: bool,
    /// Validate globally, not just locally.
    ///
    /// Default: `true`.
    pub validate_globally: bool,
    /// The amount of optimization to apply.
    ///
    /// The default depends on how [`OptimizationOptions`] is constructed.
    pub optimize_level: OptimizeLevel,
    /// The amount of effort to put into reducing module size.
    ///
    /// The default depends on how [`OptimizationOptions`] is constructed.
    pub shrink_level: ShrinkLevel,
    /// Assume traps never happen at runtime.
    ///
    /// Default: `false`.
    pub traps_never_happen: bool,
    /// Assume that the low 1K of memory is not valid for application use.
    ///
    /// Default: `false`.
    pub low_memory_unused: bool,
    /// Do faster floating point math by breaking official IEEE semantics.
    ///
    /// Default: `false`.
    pub fast_math: bool,
    /// Assume imported memory is zeroed.
    ///
    /// Default: `false`.
    pub zero_filled_memory: bool,
    /// Preserve debug info.
    ///
    /// Default: `false`.
    pub debug_info: bool,
    /// Additional pass-specific arguments.
    pub arguments: HashMap<String, String>,
}

/// The amount of optimization to apply.
///
/// This is interpreted differently by different passes.
///
/// See the documentation of various [`OptimizationOptions`]
/// constructors for a general description of how these behave.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum OptimizeLevel {
    Level0 = 0,
    Level1 = 1,
    Level2 = 2,
    Level3 = 3,
    Level4 = 4,
}

/// The amount of effort to put into reducing module size.
///
/// This is interpreted differently by different passes.
///
/// See the documentation of various [`OptimizationOptions`]
/// constructors for a general description of how these behave.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ShrinkLevel {
    Level0 = 0,
    Level1 = 1,
    Level2 = 2,
}

/// The set of optimization passes to apply.
#[derive(Clone, Debug)]
pub struct Passes {
    /// Apply the default set of optimization passes.
    pub add_default_passes: bool,
    /// Additional passes to apply.
    pub more_passes: Vec<Pass>,
}

/// Which wasm [`Feature`]s to enable and disable.
///
/// The baseline features are applied first, then
/// enabled and disabled features are applied.
#[derive(Clone, Debug, Default)]
pub struct Features {
    pub baseline: FeatureBaseline,
    pub enabled: HashSet<Feature>,
    pub disabled: HashSet<Feature>,
}

/// The set of features to apply before applying custom features.
#[derive(Clone, Debug)]
pub enum FeatureBaseline {
    /// The default Binaryen feature set.
    ///
    /// Enables [`Feature::Default`].
    /// Disables [`Feature::None`].
    Default,
    /// Only allow WebAssembly MVP features.
    ///
    /// Enables [`Feature::Mvp`].
    /// Disables [`Feature::All`].
    MvpOnly,
    /// Allow all features.
    ///
    /// Enables [`Feature::All`].
    /// Disables [Feature::Mvp`].
    All,
}

/// Constructors.
impl OptimizationOptions {
    pub(crate) fn new_empty() -> Self {
        OptimizationOptions {
            reader: ReaderOptions::default(),
            writer: WriterOptions::default(),
            inlining: InliningOptions::default(),
            passopts: PassOptions::default(),
            passes: Passes::default(),
            features: Features::default(),
            converge: false,
        }
    }

    /// Optimize for size.
    ///
    /// This corresponds to the `-Os` argument to `wasm-opt`,
    /// and also the `-O` argument to `wasm-opt`.
    ///
    /// It applies
    /// - [`Passes::add_default_passes`],
    /// - [`OptimizeLevel::Level2`],
    /// - [`ShrinkLevel::Level1`].
    pub fn new_optimize_for_size() -> Self {
        Profile::optimize_for_size().into_opts()
    }

    /// Optimize for size, but even more.
    ///
    /// It applies
    /// - [`Passes::add_default_passes`],
    /// - [`OptimizeLevel::Level2`],
    /// - [`ShrinkLevel::Level2`].
    ///
    /// This corresponds to the `-Oz` argument to `wasm-opt`.
    pub fn new_optimize_for_size_aggressively() -> Self {
        Profile::optimize_for_size_aggressively().into_opts()
    }

    /// Do not optimize.
    ///
    /// It applies
    /// - [`OptimizeLevel::Level0`],
    /// - [`ShrinkLevel::Level0`].
    ///
    /// It adds no default passes.
    ///
    /// This corresponds to the `-O0` argument to `wasm-opt`,
    /// and also to calling `wasm-opt` with no `-O*` optional at all.
    pub fn new_opt_level_0() -> Self {
        Profile::opt_level_0().into_opts()
    }

    /// Apply basic optimizations.
    ///
    /// Useful for fast iteration.
    ///
    /// It applies
    /// - [`Passes::add_default_passes`],
    /// - [`OptimizeLevel::Level1`],
    /// - [`ShrinkLevel::Level0`].
    ///
    /// This corresponds to the `-O1` argument to `wasm-opt`.
    pub fn new_opt_level_1() -> Self {
        Profile::opt_level_1().into_opts()
    }

    /// Apply most optimizations.
    ///
    /// This level of optimization is appropriate for most applications.
    /// Higher optimization levels will not necessarily yield better performance,
    /// but will take longer to optimize.
    ///
    /// It applies
    /// - [`Passes::add_default_passes`],
    /// - [`OptimizeLevel::Level2`],
    /// - [`ShrinkLevel::Level0`].
    ///
    /// This corresponds to the `-O2` argument to `wasm-opt`.
    pub fn new_opt_level_2() -> Self {
        Profile::opt_level_2().into_opts()
    }

    /// Apply slower optimizations.
    ///
    /// Spends potentially a lot of time on optimizations.
    ///
    /// It applies
    /// - [`Passes::add_default_passes`],
    /// - [`OptimizeLevel::Level3`],
    /// - [`ShrinkLevel::Level0`].
    ///
    /// This corresponds to the `-O3` argument to `wasm-opt`.
    pub fn new_opt_level_3() -> Self {
        Profile::opt_level_3().into_opts()
    }

    /// Apply the most aggressive optimizations.
    ///
    /// Flattens the IR, which can take a lot of time and memory,
    /// but may be useful on nested / complex / less-optimized input.
    ///
    /// It applies
    /// - [`Passes::add_default_passes`],
    /// - [`OptimizeLevel::Level4`],
    /// - [`ShrinkLevel::Level0`].
    ///
    /// This corresponds to the `-O4` argument to `wasm-opt`.
    pub fn new_opt_level_4() -> Self {
        Profile::opt_level_4().into_opts()
    }
}

impl Default for ReaderOptions {
    fn default() -> ReaderOptions {
        ReaderOptions {
            file_type: FileType::Any,
        }
    }
}

impl Default for WriterOptions {
    fn default() -> WriterOptions {
        WriterOptions {
            file_type: FileType::Wasm,
        }
    }
}

impl Default for InliningOptions {
    fn default() -> InliningOptions {
        InliningOptions {
            always_inline_max_size: 2,
            one_caller_inline_max_size: u32::MAX,
            flexible_inline_max_size: 20,
            allow_functions_with_loops: false,
            partial_inlining_ifs: 0,
        }
    }
}

impl Default for PassOptions {
    fn default() -> PassOptions {
        PassOptions {
            validate: true,
            validate_globally: true,
            optimize_level: OptimizeLevel::default(),
            shrink_level: ShrinkLevel::default(),
            traps_never_happen: false,
            low_memory_unused: false,
            fast_math: false,
            zero_filled_memory: false,
            debug_info: false,
            arguments: HashMap::<String, String>::new(),
        }
    }
}

impl Default for OptimizeLevel {
    fn default() -> OptimizeLevel {
        OptimizeLevel::Level0
    }
}

impl Default for ShrinkLevel {
    fn default() -> ShrinkLevel {
        ShrinkLevel::Level0
    }
}

impl Default for Passes {
    fn default() -> Passes {
        Passes {
            add_default_passes: true,
            more_passes: vec![],
        }
    }
}

impl Default for FeatureBaseline {
    fn default() -> FeatureBaseline {
        FeatureBaseline::Default
    }
}