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
/// 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 execute optimize a wasm module.
///
/// [builder-pattern]: https://rust-unofficial.github.io/patterns/patterns/creational/builder.html
#[derive(Clone, Debug, Default)]
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,
}

/// 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(Copy, 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 {
    pub always_inline_max_size: u32,
    pub one_caller_inline_max_size: u32,
    pub flexible_inline_max_size: u32,
    pub allow_functions_with_loops: bool,
    pub partial_inlining_ifs: u32,
}

/// Options that affect how optimization passes behave.
#[derive(Copy, Clone, Debug)]
pub struct PassOptions {
    pub debug: bool,
    /// Validate both the unoptimized module and the optimized module.
    ///
    /// Default: `true`.
    pub validate: bool,
    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,
    pub traps_never_happen: bool,
    pub low_memory_unused: bool,
    pub fast_math: bool,
    pub zero_filled_memory: bool,
    pub debug_info: bool,
}

/// 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)]
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)]
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>,
}

pub use crate::passes::Pass;

/// Constructors.
impl OptimizationOptions {
    /// Optimize for size.
    ///
    /// This corresponds to the `-Os` argument to `wasm-opt`,
    /// and also the `-O` argument to `wasm-opt`.
    ///
    /// It is the same as [`OptimizationOptions::default`].
    ///
    /// It applies
    /// - [`Passes::add_default_passes`],
    /// - [`OptimizeLevel::Level2`],
    /// - [`ShrinkLevel::Level1`].
    pub fn new_optimize_for_size() -> Self {
        OptimizationOptions::default()
    }

    /// 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 {
        let mut passopts = PassOptions::default();
        passopts.optimize_level = OptimizeLevel::Level2;
        passopts.shrink_level = ShrinkLevel::Level2;

        let mut opts = OptimizationOptions::default();
        opts.passopts = passopts;

        opts
    }

    /// Do not optimize.
    ///
    /// It applies
    /// - [`OptimizeLevel::Level0`],
    /// - [`ShrinkLevel::Level0`].
    ///
    /// It adds no default passes.
    ///
    /// This corresponds to the `-O0` argument to `wasm-opt`.
    pub fn new_opt_level_0() -> Self {
        let mut passopts = PassOptions::default();
        passopts.optimize_level = OptimizeLevel::Level0;
        passopts.shrink_level = ShrinkLevel::Level0;

        let mut opts = OptimizationOptions::default();
        opts.passopts = passopts;

        opts.passes.add_default_passes = false;

        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 {
        let mut passopts = PassOptions::default();
        passopts.optimize_level = OptimizeLevel::Level1;
        passopts.shrink_level = ShrinkLevel::Level0;

        let mut opts = OptimizationOptions::default();
        opts.passopts = passopts;

        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 {
        let mut passopts = PassOptions::default();
        passopts.optimize_level = OptimizeLevel::Level2;
        passopts.shrink_level = ShrinkLevel::Level0;

        let mut opts = OptimizationOptions::default();
        opts.passopts = passopts;

        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 {
        let mut passopts = PassOptions::default();
        passopts.optimize_level = OptimizeLevel::Level3;
        passopts.shrink_level = ShrinkLevel::Level0;

        let mut opts = OptimizationOptions::default();
        opts.passopts = passopts;

        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 {
        let mut passopts = PassOptions::default();
        passopts.optimize_level = OptimizeLevel::Level4;
        passopts.shrink_level = ShrinkLevel::Level0;

        let mut opts = OptimizationOptions::default();
        opts.passopts = passopts;

        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_value(),
            flexible_inline_max_size: 20,
            allow_functions_with_loops: false,
            partial_inlining_ifs: 0,
        }
    }
}

impl Default for PassOptions {
    fn default() -> PassOptions {
        PassOptions {
            debug: false,
            validate: true,
            validate_globally: false,
            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,
        }
    }
}

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

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

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