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-printing.h"
38 #include "wasm-s-parser.h"
39 #include "wasm-validator.h"
40 #include "wasm2c-wrapper.h"
41
42 #define DEBUG_TYPE "opt"
43
44 using namespace wasm;
45
46 // runs a command and returns its output TODO: portability, return code checking
runCommand(std::string command)47 static std::string runCommand(std::string command) {
48 #ifdef __linux__
49 std::string output;
50 const int MAX_BUFFER = 1024;
51 char buffer[MAX_BUFFER];
52 FILE* stream = popen(command.c_str(), "r");
53 while (fgets(buffer, MAX_BUFFER, stream) != NULL) {
54 output.append(buffer);
55 }
56 pclose(stream);
57 return output;
58 #else
59 Fatal() << "TODO: portability for wasm-opt runCommand";
60 #endif
61 }
62
willRemoveDebugInfo(const std::vector<std::string> & passes)63 static bool willRemoveDebugInfo(const std::vector<std::string>& passes) {
64 for (auto& pass : passes) {
65 if (pass == "strip" || pass == "strip-debug" || pass == "strip-dwarf") {
66 return true;
67 }
68 }
69 return false;
70 }
71
72 //
73 // main
74 //
75
main(int argc,const char * argv[])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 bool fuzzPasses = false;
85 bool fuzzMemory = true;
86 bool fuzzOOB = true;
87 std::string emitJSWrapper;
88 std::string emitSpecWrapper;
89 std::string emitWasm2CWrapper;
90 std::string inputSourceMapFilename;
91 std::string outputSourceMapFilename;
92 std::string outputSourceMapUrl;
93
94 OptimizationOptions options("wasm-opt", "Read, write, and optimize files");
95 options
96 .add("--output",
97 "-o",
98 "Output file (stdout if not specified)",
99 Options::Arguments::One,
100 [](Options* o, const std::string& argument) {
101 o->extra["output"] = argument;
102 Colors::setEnabled(false);
103 })
104 .add("--emit-text",
105 "-S",
106 "Emit text instead of binary for the output file",
107 Options::Arguments::Zero,
108 [&](Options* o, const std::string& argument) { emitBinary = false; })
109 .add("--converge",
110 "-c",
111 "Run passes to convergence, continuing while binary size decreases",
112 Options::Arguments::Zero,
113 [&](Options* o, const std::string& arguments) { converge = true; })
114 .add(
115 "--fuzz-exec-before",
116 "-feh",
117 "Execute functions before optimization, helping fuzzing find bugs",
118 Options::Arguments::Zero,
119 [&](Options* o, const std::string& arguments) { fuzzExecBefore = true; })
120 .add("--fuzz-exec",
121 "-fe",
122 "Execute functions before and after optimization, helping fuzzing "
123 "find bugs",
124 Options::Arguments::Zero,
125 [&](Options* o, const std::string& arguments) {
126 fuzzExecBefore = fuzzExecAfter = true;
127 })
128 .add("--extra-fuzz-command",
129 "-efc",
130 "An extra command to run on the output before and after optimizing. "
131 "The output is compared between the two, and an error occurs if they "
132 "are not equal",
133 Options::Arguments::One,
134 [&](Options* o, const std::string& arguments) {
135 extraFuzzCommand = arguments;
136 })
137 .add(
138 "--translate-to-fuzz",
139 "-ttf",
140 "Translate the input into a valid wasm module *somehow*, useful for "
141 "fuzzing",
142 Options::Arguments::Zero,
143 [&](Options* o, const std::string& arguments) { translateToFuzz = true; })
144 .add("--fuzz-passes",
145 "-fp",
146 "Pick a random set of passes to run, useful for fuzzing. this depends "
147 "on translate-to-fuzz (it picks the passes from the input)",
148 Options::Arguments::Zero,
149 [&](Options* o, const std::string& arguments) { fuzzPasses = true; })
150 .add("--no-fuzz-memory",
151 "",
152 "don't emit memory ops when fuzzing",
153 Options::Arguments::Zero,
154 [&](Options* o, const std::string& arguments) { fuzzMemory = false; })
155 .add("--no-fuzz-oob",
156 "",
157 "don't emit out-of-bounds loads/stores/indirect calls when fuzzing",
158 Options::Arguments::Zero,
159 [&](Options* o, const std::string& arguments) { fuzzOOB = false; })
160 .add("--emit-js-wrapper",
161 "-ejw",
162 "Emit a JavaScript wrapper file that can run the wasm with some test "
163 "values, useful for fuzzing",
164 Options::Arguments::One,
165 [&](Options* o, const std::string& arguments) {
166 emitJSWrapper = arguments;
167 })
168 .add("--emit-spec-wrapper",
169 "-esw",
170 "Emit a wasm spec interpreter wrapper file that can run the wasm with "
171 "some test values, useful for fuzzing",
172 Options::Arguments::One,
173 [&](Options* o, const std::string& arguments) {
174 emitSpecWrapper = arguments;
175 })
176 .add("--emit-wasm2c-wrapper",
177 "-esw",
178 "Emit a C wrapper file that can run the wasm after it is compiled "
179 "with wasm2c, useful for fuzzing",
180 Options::Arguments::One,
181 [&](Options* o, const std::string& arguments) {
182 emitWasm2CWrapper = arguments;
183 })
184 .add("--input-source-map",
185 "-ism",
186 "Consume source map from the specified file",
187 Options::Arguments::One,
188 [&inputSourceMapFilename](Options* o, const std::string& argument) {
189 inputSourceMapFilename = argument;
190 })
191 .add("--output-source-map",
192 "-osm",
193 "Emit source map to the specified file",
194 Options::Arguments::One,
195 [&outputSourceMapFilename](Options* o, const std::string& argument) {
196 outputSourceMapFilename = argument;
197 })
198 .add("--output-source-map-url",
199 "-osu",
200 "Emit specified string as source map URL",
201 Options::Arguments::One,
202 [&outputSourceMapUrl](Options* o, const std::string& argument) {
203 outputSourceMapUrl = argument;
204 })
205 .add_positional("INFILE",
206 Options::Arguments::One,
207 [](Options* o, const std::string& argument) {
208 o->extra["infile"] = argument;
209 });
210 options.parse(argc, argv);
211
212 Module wasm;
213
214 BYN_TRACE("reading...\n");
215
216 auto exitOnInvalidWasm = [&](const char* message) {
217 // If the user asked to print the module, print it even if invalid,
218 // as otherwise there is no way to print the broken module (the pass
219 // to print would not be reached).
220 if (std::find(options.passes.begin(), options.passes.end(), "print") !=
221 options.passes.end()) {
222 WasmPrinter::printModule(&wasm);
223 }
224 Fatal() << message;
225 };
226
227 if (!translateToFuzz) {
228 ModuleReader reader;
229 // Enable DWARF parsing if we were asked for debug info, and were not
230 // asked to remove it.
231 reader.setDWARF(options.passOptions.debugInfo &&
232 !willRemoveDebugInfo(options.passes));
233 reader.setProfile(options.profile);
234 try {
235 reader.read(options.extra["infile"], wasm, inputSourceMapFilename);
236 } catch (ParseException& p) {
237 p.dump(std::cerr);
238 std::cerr << '\n';
239 Fatal() << "error parsing wasm";
240 } catch (MapParseException& p) {
241 p.dump(std::cerr);
242 std::cerr << '\n';
243 Fatal() << "error parsing wasm source map";
244 } catch (std::bad_alloc&) {
245 Fatal() << "error building module, std::bad_alloc (possibly invalid "
246 "request for silly amounts of memory)";
247 }
248
249 options.applyFeatures(wasm);
250
251 if (options.passOptions.validate) {
252 if (!WasmValidator().validate(wasm)) {
253 exitOnInvalidWasm("error validating input");
254 }
255 }
256 } else {
257 // translate-to-fuzz
258 options.applyFeatures(wasm);
259 TranslateToFuzzReader reader(wasm, options.extra["infile"]);
260 if (fuzzPasses) {
261 reader.pickPasses(options);
262 }
263 reader.setAllowMemory(fuzzMemory);
264 reader.setAllowOOB(fuzzOOB);
265 reader.build();
266 if (options.passOptions.validate) {
267 if (!WasmValidator().validate(wasm)) {
268 WasmPrinter::printModule(&wasm);
269 Fatal() << "error after translate-to-fuzz";
270 }
271 }
272 }
273
274 if (emitJSWrapper.size() > 0) {
275 // As the code will run in JS, we must legalize it.
276 PassRunner runner(&wasm);
277 runner.add("legalize-js-interface");
278 runner.run();
279 }
280
281 ExecutionResults results;
282 if (fuzzExecBefore) {
283 results.get(wasm);
284 }
285
286 if (emitJSWrapper.size() > 0) {
287 std::ofstream outfile;
288 outfile.open(emitJSWrapper, std::ofstream::out);
289 outfile << generateJSWrapper(wasm);
290 outfile.close();
291 }
292 if (emitSpecWrapper.size() > 0) {
293 std::ofstream outfile;
294 outfile.open(emitSpecWrapper, std::ofstream::out);
295 outfile << generateSpecWrapper(wasm);
296 outfile.close();
297 }
298 if (emitWasm2CWrapper.size() > 0) {
299 std::ofstream outfile;
300 outfile.open(emitWasm2CWrapper, std::ofstream::out);
301 outfile << generateWasm2CWrapper(wasm);
302 outfile.close();
303 }
304
305 std::string firstOutput;
306
307 if (extraFuzzCommand.size() > 0 && options.extra.count("output") > 0) {
308 BYN_TRACE("writing binary before opts, for extra fuzz command...\n");
309 ModuleWriter writer;
310 writer.setBinary(emitBinary);
311 writer.setDebugInfo(options.passOptions.debugInfo);
312 writer.write(wasm, options.extra["output"]);
313 firstOutput = runCommand(extraFuzzCommand);
314 std::cout << "[extra-fuzz-command first output:]\n" << firstOutput << '\n';
315 }
316
317 if (!options.runningPasses()) {
318 if (!options.quiet) {
319 std::cerr << "warning: no passes specified, not doing any work\n";
320 }
321 } else {
322 BYN_TRACE("running passes...\n");
323 auto runPasses = [&]() {
324 options.runPasses(wasm);
325 if (options.passOptions.validate) {
326 bool valid = WasmValidator().validate(wasm);
327 if (!valid) {
328 exitOnInvalidWasm("error after opts");
329 }
330 }
331 };
332 runPasses();
333 if (converge) {
334 // Keep on running passes to convergence, defined as binary
335 // size no longer decreasing.
336 auto getSize = [&]() {
337 BufferWithRandomAccess buffer;
338 WasmBinaryWriter writer(&wasm, buffer);
339 writer.write();
340 return buffer.size();
341 };
342 auto lastSize = getSize();
343 while (1) {
344 BYN_TRACE("running iteration for convergence (" << lastSize << ")..\n");
345 runPasses();
346 auto currSize = getSize();
347 if (currSize >= lastSize) {
348 break;
349 }
350 lastSize = currSize;
351 }
352 }
353 }
354
355 if (fuzzExecAfter) {
356 results.check(wasm);
357 }
358
359 if (options.extra.count("output") == 0) {
360 if (!options.quiet) {
361 std::cerr << "warning: no output file specified, not emitting output\n";
362 }
363 return 0;
364 }
365
366 BYN_TRACE("writing...\n");
367 ModuleWriter writer;
368 writer.setBinary(emitBinary);
369 writer.setDebugInfo(options.passOptions.debugInfo);
370 if (outputSourceMapFilename.size()) {
371 writer.setSourceMapFilename(outputSourceMapFilename);
372 writer.setSourceMapUrl(outputSourceMapUrl);
373 }
374 writer.write(wasm, options.extra["output"]);
375
376 if (extraFuzzCommand.size() > 0) {
377 auto secondOutput = runCommand(extraFuzzCommand);
378 std::cout << "[extra-fuzz-command second output:]\n" << firstOutput << '\n';
379 if (firstOutput != secondOutput) {
380 Fatal() << "extra fuzz command output differs\n";
381 }
382 }
383 return 0;
384 }
385