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
43using namespace wasm;
44
45// runs a command and returns its output TODO: portability, return code checking
46static 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
62static bool
63willRemoveDebugInfo(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
76int 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

source code of dart_sdk/third_party/binaryen/src/src/tools/wasm-opt.cpp