1 | /* |
2 | * Copyright 2016 WebAssembly Community Group participants |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | |
17 | // |
18 | // A WebAssembly optimizer, loads code, optionally runs passes on it, |
19 | // then writes it. |
20 | // |
21 | |
22 | #include <memory> |
23 | |
24 | #include "execution-results.h" |
25 | #include "fuzzing.h" |
26 | #include "js-wrapper.h" |
27 | #include "optimization-options.h" |
28 | #include "pass.h" |
29 | #include "shell-interface.h" |
30 | #include "spec-wrapper.h" |
31 | #include "support/command-line.h" |
32 | #include "support/debug.h" |
33 | #include "support/file.h" |
34 | #include "wasm-binary.h" |
35 | #include "wasm-interpreter.h" |
36 | #include "wasm-io.h" |
37 | #include "wasm-s-parser.h" |
38 | #include "wasm-validator.h" |
39 | #include "wasm2c-wrapper.h" |
40 | |
41 | #define DEBUG_TYPE "opt" |
42 | |
43 | using namespace wasm; |
44 | |
45 | // runs a command and returns its output TODO: portability, return code checking |
46 | static std::string runCommand(std::string command) { |
47 | #ifdef __linux__ |
48 | std::string output; |
49 | const int MAX_BUFFER = 1024; |
50 | char buffer[MAX_BUFFER]; |
51 | FILE* stream = popen(command.c_str(), "r" ); |
52 | while (fgets(buffer, MAX_BUFFER, stream) != NULL) { |
53 | output.append(buffer); |
54 | } |
55 | pclose(stream); |
56 | return output; |
57 | #else |
58 | Fatal() << "TODO: portability for wasm-opt runCommand" ; |
59 | #endif |
60 | } |
61 | |
62 | static bool |
63 | willRemoveDebugInfo(const std::vector<OptimizationOptions::PassInfo>& passes) { |
64 | for (auto& pass : passes) { |
65 | if (PassRunner::passRemovesDebugInfo(pass.name)) { |
66 | return true; |
67 | } |
68 | } |
69 | return false; |
70 | } |
71 | |
72 | // |
73 | // main |
74 | // |
75 | |
76 | int main(int argc, const char* argv[]) { |
77 | Name entry; |
78 | bool emitBinary = true; |
79 | bool converge = false; |
80 | bool fuzzExecBefore = false; |
81 | bool fuzzExecAfter = false; |
82 | std::string extraFuzzCommand; |
83 | bool translateToFuzz = false; |
84 | std::string initialFuzz; |
85 | bool fuzzPasses = false; |
86 | bool fuzzMemory = true; |
87 | bool fuzzOOB = true; |
88 | std::string emitJSWrapper; |
89 | std::string emitSpecWrapper; |
90 | std::string emitWasm2CWrapper; |
91 | std::string inputSourceMapFilename; |
92 | std::string outputSourceMapFilename; |
93 | std::string outputSourceMapUrl; |
94 | |
95 | const std::string WasmOptOption = "wasm-opt options" ; |
96 | |
97 | OptimizationOptions options("wasm-opt" , "Read, write, and optimize files" ); |
98 | options |
99 | .add("--output" , |
100 | "-o" , |
101 | "Output file (stdout if not specified)" , |
102 | WasmOptOption, |
103 | Options::Arguments::One, |
104 | [](Options* o, const std::string& argument) { |
105 | o->extra["output" ] = argument; |
106 | Colors::setEnabled(false); |
107 | }) |
108 | .add("--emit-text" , |
109 | "-S" , |
110 | "Emit text instead of binary for the output file" , |
111 | WasmOptOption, |
112 | Options::Arguments::Zero, |
113 | [&](Options* o, const std::string& argument) { emitBinary = false; }) |
114 | .add("--converge" , |
115 | "-c" , |
116 | "Run passes to convergence, continuing while binary size decreases" , |
117 | WasmOptOption, |
118 | Options::Arguments::Zero, |
119 | [&](Options* o, const std::string& arguments) { converge = true; }) |
120 | .add( |
121 | "--fuzz-exec-before" , |
122 | "-feh" , |
123 | "Execute functions before optimization, helping fuzzing find bugs" , |
124 | WasmOptOption, |
125 | Options::Arguments::Zero, |
126 | [&](Options* o, const std::string& arguments) { fuzzExecBefore = true; }) |
127 | .add("--fuzz-exec" , |
128 | "-fe" , |
129 | "Execute functions before and after optimization, helping fuzzing " |
130 | "find bugs" , |
131 | WasmOptOption, |
132 | Options::Arguments::Zero, |
133 | [&](Options* o, const std::string& arguments) { |
134 | fuzzExecBefore = fuzzExecAfter = true; |
135 | }) |
136 | .add("--extra-fuzz-command" , |
137 | "-efc" , |
138 | "An extra command to run on the output before and after optimizing. " |
139 | "The output is compared between the two, and an error occurs if they " |
140 | "are not equal" , |
141 | WasmOptOption, |
142 | Options::Arguments::One, |
143 | [&](Options* o, const std::string& arguments) { |
144 | extraFuzzCommand = arguments; |
145 | }) |
146 | .add( |
147 | "--translate-to-fuzz" , |
148 | "-ttf" , |
149 | "Translate the input into a valid wasm module *somehow*, useful for " |
150 | "fuzzing" , |
151 | WasmOptOption, |
152 | Options::Arguments::Zero, |
153 | [&](Options* o, const std::string& arguments) { translateToFuzz = true; }) |
154 | .add("--initial-fuzz" , |
155 | "-if" , |
156 | "Initial wasm content in translate-to-fuzz (-ttf) mode" , |
157 | WasmOptOption, |
158 | Options::Arguments::One, |
159 | [&initialFuzz](Options* o, const std::string& argument) { |
160 | initialFuzz = argument; |
161 | }) |
162 | .add("--fuzz-passes" , |
163 | "-fp" , |
164 | "Pick a random set of passes to run, useful for fuzzing. this depends " |
165 | "on translate-to-fuzz (it picks the passes from the input)" , |
166 | WasmOptOption, |
167 | Options::Arguments::Zero, |
168 | [&](Options* o, const std::string& arguments) { fuzzPasses = true; }) |
169 | .add("--no-fuzz-memory" , |
170 | "" , |
171 | "don't emit memory ops when fuzzing" , |
172 | WasmOptOption, |
173 | Options::Arguments::Zero, |
174 | [&](Options* o, const std::string& arguments) { fuzzMemory = false; }) |
175 | .add("--no-fuzz-oob" , |
176 | "" , |
177 | "don't emit out-of-bounds loads/stores/indirect calls when fuzzing" , |
178 | WasmOptOption, |
179 | Options::Arguments::Zero, |
180 | [&](Options* o, const std::string& arguments) { fuzzOOB = false; }) |
181 | .add("--emit-js-wrapper" , |
182 | "-ejw" , |
183 | "Emit a JavaScript wrapper file that can run the wasm with some test " |
184 | "values, useful for fuzzing" , |
185 | WasmOptOption, |
186 | Options::Arguments::One, |
187 | [&](Options* o, const std::string& arguments) { |
188 | emitJSWrapper = arguments; |
189 | }) |
190 | .add("--emit-spec-wrapper" , |
191 | "-esw" , |
192 | "Emit a wasm spec interpreter wrapper file that can run the wasm with " |
193 | "some test values, useful for fuzzing" , |
194 | WasmOptOption, |
195 | Options::Arguments::One, |
196 | [&](Options* o, const std::string& arguments) { |
197 | emitSpecWrapper = arguments; |
198 | }) |
199 | .add("--emit-wasm2c-wrapper" , |
200 | "-esw" , |
201 | "Emit a C wrapper file that can run the wasm after it is compiled " |
202 | "with wasm2c, useful for fuzzing" , |
203 | WasmOptOption, |
204 | Options::Arguments::One, |
205 | [&](Options* o, const std::string& arguments) { |
206 | emitWasm2CWrapper = arguments; |
207 | }) |
208 | .add("--input-source-map" , |
209 | "-ism" , |
210 | "Consume source map from the specified file" , |
211 | WasmOptOption, |
212 | Options::Arguments::One, |
213 | [&inputSourceMapFilename](Options* o, const std::string& argument) { |
214 | inputSourceMapFilename = argument; |
215 | }) |
216 | .add("--output-source-map" , |
217 | "-osm" , |
218 | "Emit source map to the specified file" , |
219 | WasmOptOption, |
220 | Options::Arguments::One, |
221 | [&outputSourceMapFilename](Options* o, const std::string& argument) { |
222 | outputSourceMapFilename = argument; |
223 | }) |
224 | .add("--output-source-map-url" , |
225 | "-osu" , |
226 | "Emit specified string as source map URL" , |
227 | WasmOptOption, |
228 | Options::Arguments::One, |
229 | [&outputSourceMapUrl](Options* o, const std::string& argument) { |
230 | outputSourceMapUrl = argument; |
231 | }) |
232 | .add("--new-wat-parser" , |
233 | "" , |
234 | "Use the experimental new WAT parser" , |
235 | WasmOptOption, |
236 | Options::Arguments::Zero, |
237 | [](Options*, const std::string&) { useNewWATParser = true; }) |
238 | .add_positional("INFILE" , |
239 | Options::Arguments::One, |
240 | [](Options* o, const std::string& argument) { |
241 | o->extra["infile" ] = argument; |
242 | }); |
243 | options.parse(argc, argv); |
244 | |
245 | Module wasm; |
246 | options.applyFeatures(wasm); |
247 | |
248 | BYN_TRACE("reading...\n" ); |
249 | |
250 | auto exitOnInvalidWasm = [&](const char* message) { |
251 | // If the user asked to print the module, print it even if invalid, |
252 | // as otherwise there is no way to print the broken module (the pass |
253 | // to print would not be reached). |
254 | if (std::any_of(options.passes.begin(), |
255 | options.passes.end(), |
256 | [](const OptimizationOptions::PassInfo& info) { |
257 | return info.name == "print" ; |
258 | })) { |
259 | std::cout << wasm << '\n'; |
260 | } |
261 | Fatal() << message; |
262 | }; |
263 | |
264 | // In normal (non-translate-to-fuzz) mode we read the input file. In |
265 | // translate-to-fuzz mode the input file is the random data, and used later |
266 | // down in TranslateToFuzzReader, but there is also an optional initial fuzz |
267 | // file that if it exists we read it, then add more fuzz on top. |
268 | if (!translateToFuzz || initialFuzz.size()) { |
269 | std::string inputFile = |
270 | translateToFuzz ? initialFuzz : options.extra["infile" ]; |
271 | ModuleReader reader; |
272 | // Enable DWARF parsing if we were asked for debug info, and were not |
273 | // asked to remove it. |
274 | reader.setDWARF(options.passOptions.debugInfo && |
275 | !willRemoveDebugInfo(options.passes)); |
276 | reader.setProfile(options.profile); |
277 | try { |
278 | reader.read(inputFile, wasm, inputSourceMapFilename); |
279 | } catch (ParseException& p) { |
280 | p.dump(std::cerr); |
281 | std::cerr << '\n'; |
282 | if (options.debug) { |
283 | Fatal() << "error parsing wasm. here is what we read up to the error:\n" |
284 | << wasm; |
285 | } else { |
286 | Fatal() << "error parsing wasm (try --debug for more info)" ; |
287 | } |
288 | } catch (MapParseException& p) { |
289 | p.dump(std::cerr); |
290 | std::cerr << '\n'; |
291 | Fatal() << "error parsing wasm source map" ; |
292 | } catch (std::bad_alloc&) { |
293 | Fatal() << "error building module, std::bad_alloc (possibly invalid " |
294 | "request for silly amounts of memory)" ; |
295 | } |
296 | |
297 | if (options.passOptions.validate) { |
298 | if (!WasmValidator().validate(wasm, options.passOptions)) { |
299 | exitOnInvalidWasm("error validating input" ); |
300 | } |
301 | } |
302 | } |
303 | if (translateToFuzz) { |
304 | TranslateToFuzzReader reader(wasm, options.extra["infile" ]); |
305 | if (fuzzPasses) { |
306 | reader.pickPasses(options); |
307 | } |
308 | reader.setAllowMemory(fuzzMemory); |
309 | reader.setAllowOOB(fuzzOOB); |
310 | reader.build(); |
311 | if (options.passOptions.validate) { |
312 | if (!WasmValidator().validate(wasm, options.passOptions)) { |
313 | std::cout << wasm << '\n'; |
314 | Fatal() << "error after translate-to-fuzz" ; |
315 | } |
316 | } |
317 | } |
318 | |
319 | if (emitJSWrapper.size() > 0) { |
320 | // As the code will run in JS, we must legalize it. |
321 | PassRunner runner(&wasm); |
322 | runner.add("legalize-js-interface" ); |
323 | runner.run(); |
324 | } |
325 | |
326 | ExecutionResults results; |
327 | if (fuzzExecBefore) { |
328 | results.get(wasm); |
329 | } |
330 | |
331 | if (emitJSWrapper.size() > 0) { |
332 | std::ofstream outfile; |
333 | outfile.open(emitJSWrapper, std::ofstream::out); |
334 | outfile << generateJSWrapper(wasm); |
335 | outfile.close(); |
336 | } |
337 | if (emitSpecWrapper.size() > 0) { |
338 | std::ofstream outfile; |
339 | outfile.open(emitSpecWrapper, std::ofstream::out); |
340 | outfile << generateSpecWrapper(wasm); |
341 | outfile.close(); |
342 | } |
343 | if (emitWasm2CWrapper.size() > 0) { |
344 | std::ofstream outfile; |
345 | outfile.open(emitWasm2CWrapper, std::ofstream::out); |
346 | outfile << generateWasm2CWrapper(wasm); |
347 | outfile.close(); |
348 | } |
349 | |
350 | std::string firstOutput; |
351 | |
352 | if (extraFuzzCommand.size() > 0 && options.extra.count("output" ) > 0) { |
353 | BYN_TRACE("writing binary before opts, for extra fuzz command...\n" ); |
354 | ModuleWriter writer; |
355 | writer.setBinary(emitBinary); |
356 | writer.setDebugInfo(options.passOptions.debugInfo); |
357 | writer.write(wasm, output&: options.extra["output" ]); |
358 | firstOutput = runCommand(extraFuzzCommand); |
359 | std::cout << "[extra-fuzz-command first output:]\n" << firstOutput << '\n'; |
360 | } |
361 | |
362 | if (!options.runningPasses()) { |
363 | if (!options.quiet) { |
364 | std::cerr << "warning: no passes specified, not doing any work\n" ; |
365 | } |
366 | } else { |
367 | BYN_TRACE("running passes...\n" ); |
368 | auto runPasses = [&]() { |
369 | options.runPasses(wasm); |
370 | if (options.passOptions.validate) { |
371 | bool valid = WasmValidator().validate(wasm, options.passOptions); |
372 | if (!valid) { |
373 | exitOnInvalidWasm("error after opts" ); |
374 | } |
375 | } |
376 | }; |
377 | runPasses(); |
378 | if (converge) { |
379 | // Keep on running passes to convergence, defined as binary |
380 | // size no longer decreasing. |
381 | auto getSize = [&]() { |
382 | BufferWithRandomAccess buffer; |
383 | WasmBinaryWriter writer(&wasm, buffer); |
384 | writer.write(); |
385 | return buffer.size(); |
386 | }; |
387 | auto lastSize = getSize(); |
388 | while (1) { |
389 | BYN_TRACE("running iteration for convergence (" << lastSize << ")..\n" ); |
390 | runPasses(); |
391 | auto currSize = getSize(); |
392 | if (currSize >= lastSize) { |
393 | break; |
394 | } |
395 | lastSize = currSize; |
396 | } |
397 | } |
398 | } |
399 | |
400 | if (fuzzExecAfter) { |
401 | results.check(wasm); |
402 | } |
403 | |
404 | if (options.extra.count("output" ) == 0) { |
405 | if (!options.quiet) { |
406 | std::cerr << "warning: no output file specified, not emitting output\n" ; |
407 | } |
408 | return 0; |
409 | } |
410 | |
411 | BYN_TRACE("writing...\n" ); |
412 | ModuleWriter writer; |
413 | writer.setBinary(emitBinary); |
414 | writer.setDebugInfo(options.passOptions.debugInfo); |
415 | if (outputSourceMapFilename.size()) { |
416 | writer.setSourceMapFilename(outputSourceMapFilename); |
417 | writer.setSourceMapUrl(outputSourceMapUrl); |
418 | } |
419 | writer.write(wasm, output&: options.extra["output" ]); |
420 | |
421 | if (extraFuzzCommand.size() > 0) { |
422 | auto secondOutput = runCommand(extraFuzzCommand); |
423 | std::cout << "[extra-fuzz-command second output:]\n" << firstOutput << '\n'; |
424 | if (firstOutput != secondOutput) { |
425 | Fatal() << "extra fuzz command output differs\n" ; |
426 | } |
427 | } |
428 | return 0; |
429 | } |
430 | |