| /* | |
| * Copyright 2016 WebAssembly Community Group participants | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| // | |
| // A WebAssembly optimizer, loads code, optionally runs passes on it, | |
| // then writes it. | |
| // | |
| #include <memory> | |
| #include "execution-results.h" | |
| #include "fuzzing.h" | |
| #include "optimization-options.h" | |
| #include "pass.h" | |
| #include "shell-interface.h" | |
| #include "spec-wrapper.h" | |
| #include "support/command-line.h" | |
| #include "support/debug.h" | |
| #include "support/file.h" | |
| #include "support/path.h" | |
| #include "wasm-binary.h" | |
| #include "wasm-interpreter.h" | |
| #include "wasm-io.h" | |
| #include "wasm-stack.h" | |
| #include "wasm-validator.h" | |
| #include "wasm2c-wrapper.h" | |
| #define DEBUG_TYPE "opt" | |
| using namespace wasm; | |
| // runs a command and returns its output TODO: portability, return code checking | |
| static std::string runCommand(std::string command) { | |
| #ifdef __linux__ | |
| std::string output; | |
| const int MAX_BUFFER = 1024; | |
| char buffer[MAX_BUFFER]; | |
| FILE* stream = popen(command.c_str(), "r"); | |
| while (fgets(buffer, MAX_BUFFER, stream) != NULL) { | |
| output.append(buffer); | |
| } | |
| pclose(stream); | |
| return output; | |
| #else | |
| Fatal() << "TODO: portability for wasm-opt runCommand"; | |
| #endif | |
| } | |
| static bool | |
| willRemoveDebugInfo(const std::vector<OptimizationOptions::PassInfo>& passes) { | |
| for (auto& pass : passes) { | |
| if (PassRunner::passRemovesDebugInfo(pass.name)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| // | |
| // main | |
| // | |
| int main(int argc, const char* argv[]) { | |
| Name entry; | |
| bool emitBinary = true; | |
| bool converge = false; | |
| bool fuzzExecBefore = false; | |
| bool fuzzExecAfter = false; | |
| std::string fuzzExecSecond; | |
| std::string extraFuzzCommand; | |
| bool translateToFuzz = false; | |
| std::string initialFuzz; | |
| bool fuzzPasses = false; | |
| bool fuzzMemory = true; | |
| bool fuzzOOB = true; | |
| bool fuzzPreserveImportsAndExports = false; | |
| bool fuzzAgainstJS = false; | |
| std::string fuzzImport; | |
| std::string emitSpecWrapper; | |
| std::string emitWasm2CWrapper; | |
| std::string inputSourceMapFilename; | |
| std::string outputSourceMapFilename; | |
| std::string outputSourceMapUrl; | |
| bool emitExnref = false; | |
| const std::string WasmOptOption = "wasm-opt options"; | |
| OptimizationOptions options("wasm-opt", | |
| R"(Read, write, and optimize files. | |
| Example usage: | |
| wasm-opt input.wasm -O3 -o output.wasm | |
| This reads an input wasm file, optimizes with -O3, and writes out the result. | |
| For more on how to optimize effectively, see | |
| https://github.com/WebAssembly/binaryen/wiki/Optimizer-Cookbook | |
| https://github.com/WebAssembly/binaryen/wiki/GC-Optimization-Guidebook | |
| )"); | |
| options | |
| .add("--output", | |
| "-o", | |
| "Output file", | |
| WasmOptOption, | |
| Options::Arguments::One, | |
| [](Options* o, const std::string& argument) { | |
| o->extra["output"] = argument; | |
| Colors::setEnabled(false); | |
| }) | |
| .add("--emit-text", | |
| "-S", | |
| "Emit text instead of binary for the output file", | |
| WasmOptOption, | |
| Options::Arguments::Zero, | |
| [&](Options* o, const std::string& argument) { emitBinary = false; }) | |
| .add("--converge", | |
| "-c", | |
| "Run passes to convergence, continuing while binary size decreases", | |
| WasmOptOption, | |
| Options::Arguments::Zero, | |
| [&](Options* o, const std::string& arguments) { converge = true; }) | |
| .add( | |
| "--fuzz-exec-before", | |
| "-feh", | |
| "Execute functions before optimization, helping fuzzing find bugs", | |
| WasmOptOption, | |
| Options::Arguments::Zero, | |
| [&](Options* o, const std::string& arguments) { fuzzExecBefore = true; }) | |
| .add("--fuzz-exec", | |
| "-fe", | |
| "Execute functions before and after optimization, helping fuzzing " | |
| "find bugs", | |
| WasmOptOption, | |
| Options::Arguments::Zero, | |
| [&](Options* o, const std::string& arguments) { | |
| fuzzExecBefore = fuzzExecAfter = true; | |
| }) | |
| .add("--fuzz-exec-second", | |
| "", | |
| "A second module to link with the first, for fuzz-exec-before (only " | |
| "before, as optimizations are not applied to it)", | |
| WasmOptOption, | |
| Options::Arguments::One, | |
| [&](Options* o, const std::string& arguments) { | |
| fuzzExecSecond = arguments; | |
| }) | |
| .add("--extra-fuzz-command", | |
| "-efc", | |
| "An extra command to run on the output before and after optimizing. " | |
| "The output is compared between the two, and an error occurs if they " | |
| "are not equal", | |
| WasmOptOption, | |
| Options::Arguments::One, | |
| [&](Options* o, const std::string& arguments) { | |
| extraFuzzCommand = arguments; | |
| }) | |
| .add( | |
| "--translate-to-fuzz", | |
| "-ttf", | |
| "Translate the input into a valid wasm module *somehow*, useful for " | |
| "fuzzing", | |
| WasmOptOption, | |
| Options::Arguments::Zero, | |
| [&](Options* o, const std::string& arguments) { translateToFuzz = true; }) | |
| .add("--initial-fuzz", | |
| "-if", | |
| "Initial wasm content in translate-to-fuzz (-ttf) mode", | |
| WasmOptOption, | |
| Options::Arguments::One, | |
| [&initialFuzz](Options* o, const std::string& argument) { | |
| initialFuzz = argument; | |
| }) | |
| .add("--fuzz-passes", | |
| "-fp", | |
| "When doing translate-to-fuzz, pick a set of random passes from the " | |
| "input to further shape the wasm", | |
| WasmOptOption, | |
| Options::Arguments::Zero, | |
| [&](Options* o, const std::string& arguments) { fuzzPasses = true; }) | |
| .add("--no-fuzz-memory", | |
| "", | |
| "don't emit memory ops when fuzzing", | |
| WasmOptOption, | |
| Options::Arguments::Zero, | |
| [&](Options* o, const std::string& arguments) { fuzzMemory = false; }) | |
| .add("--no-fuzz-oob", | |
| "", | |
| "don't emit out-of-bounds loads/stores/indirect calls when fuzzing", | |
| WasmOptOption, | |
| Options::Arguments::Zero, | |
| [&](Options* o, const std::string& arguments) { fuzzOOB = false; }) | |
| .add("--fuzz-preserve-imports-exports", | |
| "", | |
| "don't add imports and exports in -ttf mode, and keep the start", | |
| WasmOptOption, | |
| Options::Arguments::Zero, | |
| [&](Options* o, const std::string& arguments) { | |
| fuzzPreserveImportsAndExports = true; | |
| }) | |
| .add( | |
| "--fuzz-against-js", | |
| "", | |
| "modify the wasm in valid ways that assume it is used only from JS", | |
| WasmOptOption, | |
| Options::Arguments::Zero, | |
| [&](Options* o, const std::string& arguments) { fuzzAgainstJS = true; }) | |
| .add( | |
| "--fuzz-import", | |
| "", | |
| "a module to use as an import in -ttf mode", | |
| WasmOptOption, | |
| Options::Arguments::One, | |
| [&](Options* o, const std::string& arguments) { fuzzImport = arguments; }) | |
| .add("--emit-spec-wrapper", | |
| "-esw", | |
| "Emit a wasm spec interpreter wrapper file that can run the wasm with " | |
| "some test values, useful for fuzzing", | |
| WasmOptOption, | |
| Options::Arguments::One, | |
| [&](Options* o, const std::string& arguments) { | |
| emitSpecWrapper = arguments; | |
| }) | |
| .add("--emit-wasm2c-wrapper", | |
| "-esw", | |
| "Emit a C wrapper file that can run the wasm after it is compiled " | |
| "with wasm2c, useful for fuzzing", | |
| WasmOptOption, | |
| Options::Arguments::One, | |
| [&](Options* o, const std::string& arguments) { | |
| emitWasm2CWrapper = arguments; | |
| }) | |
| .add("--input-source-map", | |
| "-ism", | |
| "Consume source map from the specified file", | |
| WasmOptOption, | |
| Options::Arguments::One, | |
| [&inputSourceMapFilename](Options* o, const std::string& argument) { | |
| inputSourceMapFilename = argument; | |
| }) | |
| .add("--output-source-map", | |
| "-osm", | |
| "Emit source map to the specified file", | |
| WasmOptOption, | |
| Options::Arguments::One, | |
| [&outputSourceMapFilename](Options* o, const std::string& argument) { | |
| outputSourceMapFilename = argument; | |
| }) | |
| .add("--output-source-map-url", | |
| "-osu", | |
| "Emit specified string as source map URL", | |
| WasmOptOption, | |
| Options::Arguments::One, | |
| [&outputSourceMapUrl](Options* o, const std::string& argument) { | |
| outputSourceMapUrl = argument; | |
| }) | |
| .add_positional("INFILE", | |
| Options::Arguments::One, | |
| [](Options* o, const std::string& argument) { | |
| o->extra["infile"] = argument; | |
| }) | |
| .add("--experimental-new-eh", | |
| "", | |
| "Deprecated; same as --emit-exnref", | |
| WasmOptOption, | |
| Options::Arguments::Zero, | |
| [&emitExnref](Options*, const std::string&) { emitExnref = true; }) | |
| .add("--emit-exnref", | |
| "", | |
| "After running all requested transformations / optimizations, " | |
| "translate the instruction to use the new EH instructions at the end. " | |
| "Depending on the optimization level specified, this may do some more " | |
| "post-translation optimizations.", | |
| WasmOptOption, | |
| Options::Arguments::Zero, | |
| [&emitExnref](Options*, const std::string&) { emitExnref = true; }); | |
| options.parse(argc, argv); | |
| Module wasm; | |
| options.applyOptionsBeforeParse(wasm); | |
| BYN_TRACE("reading...\n"); | |
| auto exitOnInvalidWasm = [&](const char* message) { | |
| // If the user asked to print the module, print it even if invalid, | |
| // as otherwise there is no way to print the broken module (the pass | |
| // to print would not be reached). | |
| if (std::any_of(options.passes.begin(), | |
| options.passes.end(), | |
| [](const OptimizationOptions::PassInfo& info) { | |
| return info.name == "print"; | |
| })) { | |
| std::cout << wasm << '\n'; | |
| } | |
| Fatal() << message; | |
| }; | |
| // In normal (non-translate-to-fuzz) mode we read the input file. In | |
| // translate-to-fuzz mode the input file is the random data, and used later | |
| // down in TranslateToFuzzReader, but there is also an optional initial fuzz | |
| // file that if it exists we read it, then add more fuzz on top. | |
| if (!translateToFuzz || initialFuzz.size()) { | |
| std::string inputFile = | |
| translateToFuzz ? initialFuzz : options.extra["infile"]; | |
| ModuleReader reader; | |
| // Enable DWARF parsing if we were asked for debug info, and were not | |
| // asked to remove it. | |
| reader.setDWARF(options.passOptions.debugInfo && | |
| !willRemoveDebugInfo(options.passes)); | |
| reader.setProfile(options.profile); | |
| try { | |
| reader.read(inputFile, wasm, inputSourceMapFilename); | |
| } catch (ParseException& p) { | |
| p.dump(std::cerr); | |
| std::cerr << '\n'; | |
| if (options.debug) { | |
| Fatal() << "error parsing wasm. here is what we read up to the error:\n" | |
| << wasm; | |
| } else { | |
| Fatal() << "error parsing wasm (try --debug for more info)"; | |
| } | |
| } catch (MapParseException& p) { | |
| p.dump(std::cerr); | |
| std::cerr << '\n'; | |
| Fatal() << "error parsing wasm source map"; | |
| } catch (std::bad_alloc&) { | |
| Fatal() << "error building module, std::bad_alloc (possibly invalid " | |
| "request for silly amounts of memory)"; | |
| } | |
| options.applyOptionsAfterParse(wasm); | |
| if (options.passOptions.validate) { | |
| if (!WasmValidator().validate(wasm, options.passOptions)) { | |
| exitOnInvalidWasm("error validating input"); | |
| } | |
| } | |
| } | |
| if (translateToFuzz) { | |
| TranslateToFuzzReader reader( | |
| wasm, options.extra["infile"], options.passOptions.worldMode); | |
| reader.setAllowMemory(fuzzMemory); | |
| reader.setAllowOOB(fuzzOOB); | |
| reader.setPreserveImportsAndExports(fuzzPreserveImportsAndExports); | |
| reader.setAgainstJS(fuzzAgainstJS); | |
| if (!fuzzImport.empty()) { | |
| reader.setImportedModule(fuzzImport); | |
| } | |
| if (fuzzPasses) { | |
| reader.pickPasses(options); | |
| } | |
| reader.build(); | |
| if (options.passOptions.validate) { | |
| if (!WasmValidator().validate(wasm, options.passOptions)) { | |
| std::cout << wasm << '\n'; | |
| Fatal() << "error after translate-to-fuzz"; | |
| } | |
| } | |
| } | |
| ExecutionResults results; | |
| if (fuzzExecBefore) { | |
| if (fuzzExecSecond.empty()) { | |
| results.collect(wasm); | |
| } else { | |
| // Add the second module. | |
| Module second; | |
| second.features = wasm.features; | |
| ModuleReader().read(fuzzExecSecond, second); | |
| results.collect(wasm, &second); | |
| } | |
| } | |
| if (emitSpecWrapper.size() > 0) { | |
| std::ofstream outfile; | |
| outfile.open(wasm::Path::to_path(emitSpecWrapper), std::ofstream::out); | |
| outfile << generateSpecWrapper(wasm); | |
| outfile.close(); | |
| } | |
| if (emitWasm2CWrapper.size() > 0) { | |
| std::ofstream outfile; | |
| outfile.open(wasm::Path::to_path(emitWasm2CWrapper), std::ofstream::out); | |
| outfile << generateWasm2CWrapper(wasm); | |
| outfile.close(); | |
| } | |
| std::string firstOutput; | |
| if (extraFuzzCommand.size() > 0 && options.extra.contains("output")) { | |
| BYN_TRACE("writing binary before opts, for extra fuzz command...\n"); | |
| ModuleWriter writer(options.passOptions); | |
| writer.setBinary(emitBinary); | |
| writer.setDebugInfo(options.passOptions.debugInfo); | |
| options.write(writer, wasm, options.extra["output"]); | |
| firstOutput = runCommand(extraFuzzCommand); | |
| std::cout << "[extra-fuzz-command first output:]\n" << firstOutput << '\n'; | |
| } | |
| bool translateToExnref = wasm.features.hasExceptionHandling() && emitExnref; | |
| if (!options.runningPasses()) { | |
| if (!options.quiet && !translateToExnref) { | |
| std::cerr << "warning: no passes specified, not doing any work\n"; | |
| } | |
| } else { | |
| BYN_TRACE("running passes...\n"); | |
| auto runPasses = [&]() { | |
| options.runPasses(wasm); | |
| if (options.passOptions.validate) { | |
| bool valid = WasmValidator().validate(wasm, options.passOptions); | |
| if (!valid) { | |
| exitOnInvalidWasm("error after opts"); | |
| } | |
| } | |
| }; | |
| runPasses(); | |
| if (converge) { | |
| // Keep on running passes to convergence, defined as binary | |
| // size no longer decreasing. | |
| auto getSize = [&]() { | |
| BufferWithRandomAccess buffer; | |
| WasmBinaryWriter writer(&wasm, buffer, options.passOptions); | |
| writer.write(); | |
| return buffer.size(); | |
| }; | |
| auto lastSize = getSize(); | |
| while (1) { | |
| BYN_TRACE("running iteration for convergence (" << lastSize << ")..\n"); | |
| runPasses(); | |
| auto currSize = getSize(); | |
| if (currSize >= lastSize) { | |
| break; | |
| } | |
| lastSize = currSize; | |
| } | |
| } | |
| } | |
| if (translateToExnref) { | |
| BYN_TRACE("translating to new EH instructions...\n"); | |
| PassRunner runner(&wasm, options.passOptions); | |
| runner.add("translate-to-exnref"); | |
| runner.run(); | |
| if (options.passOptions.validate) { | |
| bool valid = WasmValidator().validate(wasm, options.passOptions); | |
| if (!valid) { | |
| exitOnInvalidWasm("error after opts"); | |
| } | |
| } | |
| } | |
| if (fuzzExecAfter) { | |
| results.check(wasm); | |
| } | |
| if (options.passOptions.printStackIR) { | |
| printStackIR(std::cout, &wasm, options.passOptions); | |
| } | |
| if (!options.extra.contains("output")) { | |
| if (!options.quiet) { | |
| bool printsToStdout = std::any_of( | |
| options.passes.begin(), | |
| options.passes.end(), | |
| [](const OptimizationOptions::PassInfo& info) { | |
| return info.name == "print" || info.name == "print-function-map"; | |
| }); | |
| if (!printsToStdout) { | |
| std::cerr << "warning: no output file specified, not emitting output\n"; | |
| } | |
| } | |
| flush_and_quick_exit(0); | |
| return 0; | |
| } | |
| // Ensure the destructor of ModuleWriter runs before quick_exit. | |
| { | |
| BYN_TRACE("writing...\n"); | |
| ModuleWriter writer(options.passOptions); | |
| writer.setBinary(emitBinary); | |
| writer.setDebugInfo(options.passOptions.debugInfo); | |
| if (outputSourceMapFilename.size()) { | |
| writer.setSourceMapFilename(outputSourceMapFilename); | |
| writer.setSourceMapUrl(outputSourceMapUrl); | |
| } | |
| options.write(writer, wasm, options.extra["output"]); | |
| } | |
| if (extraFuzzCommand.size() > 0) { | |
| auto secondOutput = runCommand(extraFuzzCommand); | |
| std::cout << "[extra-fuzz-command second output:]\n" << firstOutput << '\n'; | |
| if (firstOutput != secondOutput) { | |
| Fatal() << "extra fuzz command output differs\n"; | |
| } | |
| } | |
| flush_and_quick_exit(0); | |
| } |