1use crate::api::{Feature, FileType, OptimizationOptions, OptimizeLevel, Pass, ShrinkLevel};
13use crate::profiles::Profile;
14use crate::run::OptimizationError;
15use std::ffi::{OsStr, OsString};
16use std::iter::Iterator;
17use std::num::ParseIntError;
18use std::path::PathBuf;
19use std::result::Result;
20use std::str::FromStr;
21use strum::IntoEnumIterator;
22use thiserror::Error;
23
24pub use crate::fake_command::Command;
25
26pub fn run_from_command_args(command: Command) -> Result<(), Error> {
59 let parsed = parse_command_args(command)?;
60
61 parsed.opts.run_with_sourcemaps(
62 parsed.input_file,
63 parsed.input_sourcemap,
64 parsed.output_file,
65 parsed.output_sourcemap,
66 parsed.sourcemap_url,
67 )?;
68
69 Ok(())
70}
71
72#[derive(Error, Debug)]
74pub enum Error {
75 #[error("An input file is required")]
77 InputFileRequired,
78 #[error("The `-o` option to `wasm-opt` is required")]
80 OutputFileRequired,
81 #[error("The `wasm-opt` argument list ended while expecting another argument")]
83 UnexpectedEndOfArgs,
84 #[error("Argument must be unicode: {arg:?}")]
86 NeedUnicode { arg: OsString },
87 #[error("Argument must be a number: {arg:?}")]
89 NeedNumber {
90 arg: OsString,
91 #[source]
92 source: ParseIntError,
93 },
94 #[error("Unsupported `wasm-opt` command-line arguments: {args:?}")]
96 Unsupported { args: Vec<OsString> },
97 #[error("Error while optimization wasm modules")]
99 ExecutionError(
100 #[from]
101 #[source]
102 OptimizationError,
103 ),
104}
105
106struct ParsedCliArgs {
107 opts: OptimizationOptions,
108 input_file: PathBuf,
109 input_sourcemap: Option<PathBuf>,
110 output_file: PathBuf,
111 output_sourcemap: Option<PathBuf>,
112 sourcemap_url: Option<String>,
113}
114
115#[rustfmt::skip]
116fn parse_command_args(command: Command) -> Result<ParsedCliArgs, Error> {
117 let mut opts = OptimizationOptions::new_opt_level_0();
118
119 let mut args = command.get_args();
120
121 let mut input_file: Option<PathBuf> = None;
122 let mut input_sourcemap: Option<PathBuf> = None;
123 let mut output_file: Option<PathBuf> = None;
124 let mut output_sourcemap: Option<PathBuf> = None;
125 let mut sourcemap_url: Option<String> = None;
126
127 let mut unsupported: Vec<OsString> = vec![];
128
129 while let Some(arg) = args.next() {
130 let arg = if let Some(arg) = arg.to_str() {
131 arg
132 } else {
133 parse_infile_path(arg, &mut input_file, &mut unsupported);
135 continue;
136 };
137
138 match arg {
140 "--output" | "-o" => {
143 parse_path_into(&mut args, &mut output_file, &mut unsupported)?;
144 }
145 "--emit-text" | "-S" => {
146 opts.writer_file_type(FileType::Wat);
147 }
148 "--converge" | "-c" => {
149 opts.set_converge();
150 }
151 "--input-source-map" | "-ism" => {
152 parse_path_into(&mut args, &mut input_sourcemap, &mut unsupported)?;
153 }
154 "--output-source-map" | "-osm" => {
155 parse_path_into(&mut args, &mut output_sourcemap, &mut unsupported)?;
156 }
157 "--output-source-map-url" | "-osu" => {
158 sourcemap_url = Some(parse_unicode(&mut args)?);
159 }
160
161 "-O" => {
164 Profile::optimize_for_size().apply_to_opts(&mut opts);
165 }
166 "-O0" => {
167 Profile::opt_level_0().apply_to_opts(&mut opts);
168 }
169 "-O1" => {
170 Profile::opt_level_1().apply_to_opts(&mut opts);
171 }
172 "-O2" => {
173 Profile::opt_level_2().apply_to_opts(&mut opts);
174 }
175 "-O3" => {
176 Profile::opt_level_3().apply_to_opts(&mut opts);
177 }
178 "-O4" => {
179 Profile::opt_level_4().apply_to_opts(&mut opts);
180 }
181 "-Os" => {
182 Profile::optimize_for_size().apply_to_opts(&mut opts);
183 }
184 "-Oz" => {
185 Profile::optimize_for_size_aggressively().apply_to_opts(&mut opts);
186 }
187 "--optimize-level" | "-ol" => {
188 match parse_unicode(&mut args)?.as_str() {
189 "0" => { opts.optimize_level(OptimizeLevel::Level0); },
190 "1" => { opts.optimize_level(OptimizeLevel::Level1); },
191 "2" => { opts.optimize_level(OptimizeLevel::Level2); },
192 "3" => { opts.optimize_level(OptimizeLevel::Level3); },
193 "4" => { opts.optimize_level(OptimizeLevel::Level4); },
194 arg => {
195 unsupported.push(OsString::from(arg));
196 }
197 }
198 }
199 "--shrink-level" | "-s" => {
200 match parse_unicode(&mut args)?.as_str() {
201 "0" => { opts.shrink_level(ShrinkLevel::Level0); },
202 "1" => { opts.shrink_level(ShrinkLevel::Level1); },
203 "2" => { opts.shrink_level(ShrinkLevel::Level2); },
204 arg => {
205 unsupported.push(OsString::from(arg));
206 }
207 }
208 }
209 "--debuginfo" | "-g" => {
210 opts.debug_info(true);
211 }
212 "--always-inline-max-function-size" | "-aimfs" => {
213 opts.always_inline_max_size(parse_u32(&mut args)?);
214 }
215 "--flexible-inline-max-function-size" | "-fimfs" => {
216 opts.flexible_inline_max_size(parse_u32(&mut args)?);
217 }
218 "--one-caller-inline-max-function-size" | "-ocifms" => {
219 opts.one_caller_inline_max_size(parse_u32(&mut args)?);
220 }
221 "--inline-functions-with-loops" | "-ifwl" => {
222 opts.allow_functions_with_loops(true);
223 }
224 "--partial-inlining-ifs" | "-pii" => {
225 opts.partial_inlining_ifs(parse_u32(&mut args)?);
226 }
227 "--traps-never-happen" | "-tnh" => {
228 opts.traps_never_happen(true);
229 }
230 "--low-memory-unused" | "-lmu" => {
231 opts.low_memory_unused(true);
232 }
233 "--fast-math" | "-ffm" => {
234 opts.fast_math(true);
235 }
236 "--zero-filled-memory" | "-uim" => {
237 opts.zero_filled_memory(true);
238 }
239
240 "--mvp-features" | "-mvp" => {
243 opts.mvp_features_only();
244 }
245 "--all-features" | "-all" => {
246 opts.all_features();
247 }
248 "--quiet" | "-q" => {
249 }
251 "--no-validation" | "-n" => {
252 opts.validate(false);
253 }
254 "--pass-arg" | "-pa" => {
255 let args = parse_unicode(&mut args)?;
256 if args.contains("@") {
257 let args: Vec<&str> = args.split("@").collect();
258 opts.set_pass_arg(args[0], args[1]);
259 } else {
260 opts.set_pass_arg(&args, "1");
261 }
262 }
263
264 _ => {
267 if arg.starts_with("--enable-") {
270 let feature = &arg[9..];
271 if let Ok(feature) = Feature::from_str(feature) {
272 opts.enable_feature(feature);
273 } else {
274 unsupported.push(OsString::from(arg));
275 }
276 } else if arg.starts_with("--disable-") {
277 let feature = &arg[10..];
278 if let Ok(feature) = Feature::from_str(feature) {
279 opts.disable_feature(feature);
280 } else {
281 unsupported.push(OsString::from(arg));
282 }
283 } else {
284 let mut is_pass = false;
285 for pass in Pass::iter() {
286 if is_pass_argument(arg, &pass) {
287 opts.add_pass(pass);
288 is_pass = true;
289 }
290 }
291
292 if !is_pass {
293 if arg.starts_with("-") && arg.len() > 1 {
294 unsupported.push(OsString::from(arg));
296 } else {
297 parse_infile_path(OsStr::new(arg), &mut input_file, &mut unsupported);
298 }
299 }
300 }
301 }
302 }
303 }
304
305 let input_file = if let Some(input_file) = input_file {
306 input_file
307 } else {
308 return Err(Error::InputFileRequired);
309 };
310 let output_file = if let Some(output_file) = output_file {
311 output_file
312 } else {
313 return Err(Error::OutputFileRequired);
314 };
315
316 if unsupported.len() > 0 {
317 return Err(Error::Unsupported {
318 args: unsupported,
319 });
320 }
321
322 Ok(ParsedCliArgs {
323 opts,
324 input_file,
325 input_sourcemap,
326 output_file,
327 output_sourcemap,
328 sourcemap_url,
329 })
330}
331
332fn is_pass_argument(arg: &str, pass: &Pass) -> bool {
333 let pass_name = pass.name();
334 arg.starts_with("--") && arg.contains(pass_name) && arg.len() == 2 + pass_name.len()
335}
336
337fn parse_infile_path(
338 arg: &OsStr,
339 maybe_input_file: &mut Option<PathBuf>,
340 unsupported: &mut Vec<OsString>,
341) {
342 parse_path_into(&mut Some(arg).into_iter(), maybe_input_file, unsupported).expect("impossible")
343}
344
345fn parse_path_into<'item>(
346 args: &mut impl Iterator<Item = &'item OsStr>,
347 maybe_path: &mut Option<PathBuf>,
348 unsupported: &mut Vec<OsString>,
349) -> Result<(), Error> {
350 if let Some(arg) = args.next() {
351 if maybe_path.is_none() {
352 *maybe_path = Some(PathBuf::from(arg));
353 } else {
354 unsupported.push(OsString::from(arg));
355 }
356
357 Ok(())
358 } else {
359 Err(Error::UnexpectedEndOfArgs)
360 }
361}
362
363fn parse_unicode<'item>(args: &mut impl Iterator<Item = &'item OsStr>) -> Result<String, Error> {
364 if let Some(arg) = args.next() {
365 if let Some(arg) = arg.to_str() {
366 Ok(arg.to_owned())
367 } else {
368 Err(Error::NeedUnicode {
369 arg: arg.to_owned(),
370 })
371 }
372 } else {
373 Err(Error::UnexpectedEndOfArgs)
374 }
375}
376
377fn parse_u32<'item>(args: &mut impl Iterator<Item = &'item OsStr>) -> Result<u32, Error> {
378 let arg = parse_unicode(args)?;
379 let number: u32 = arg.parse().map_err(|e| Error::NeedNumber {
380 arg: OsString::from(arg),
381 source: e,
382 })?;
383 Ok(number)
384}