| 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 | |