diff options
author | Thomas Lively <7121787+tlively@users.noreply.github.com> | 2021-09-03 08:24:08 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-03 08:24:08 -0700 |
commit | 548d1971c3c844e8dab8c0da4e97aa5c339937df (patch) | |
tree | 188cec7d93b7743f987c101e6a8d8999d3c1d62d /src/tools/wasm-split/split-options.cpp | |
parent | e84980dffb62d672c991960a701a3c7de8f8aa74 (diff) | |
download | binaryen-548d1971c3c844e8dab8c0da4e97aa5c339937df.tar.gz binaryen-548d1971c3c844e8dab8c0da4e97aa5c339937df.tar.bz2 binaryen-548d1971c3c844e8dab8c0da4e97aa5c339937df.zip |
[NFC] Split wasm-split into multiple files (#4119)
As wasm-split has gained new functionality, its implementation file has become
large. In preparation for adding even more functionality, split the existing
implementation across multiple files in a new tools/wasm-split subdirectory.
Diffstat (limited to 'src/tools/wasm-split/split-options.cpp')
-rw-r--r-- | src/tools/wasm-split/split-options.cpp | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/src/tools/wasm-split/split-options.cpp b/src/tools/wasm-split/split-options.cpp new file mode 100644 index 000000000..419555f45 --- /dev/null +++ b/src/tools/wasm-split/split-options.cpp @@ -0,0 +1,356 @@ +/* + * Copyright 2021 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. + */ + +#include "split-options.h" +#include <fstream> + +namespace wasm { + +namespace { + +std::set<Name> parseNameListFromLine(const std::string& line) { + std::set<Name> names; + std::istringstream stream(line); + for (std::string name; std::getline(stream, name, ',');) { + names.insert(name); + } + return names; +} + +std::set<Name> parseNameListFromFile(const std::string& filename) { + std::ifstream infile(filename); + if (!infile.is_open()) { + std::cerr << "Failed opening '" << filename << "'" << std::endl; + exit(EXIT_FAILURE); + } + + std::set<Name> names; + std::string line; + while (std::getline(infile, line)) { + if (line.length() > 0) { + names.insert(line); + } + } + + return names; +} + +std::set<Name> parseNameList(const std::string& listOrFile) { + if (!listOrFile.empty() && listOrFile[0] == '@') { + return parseNameListFromFile(listOrFile.substr(1)); + } + + return parseNameListFromLine(listOrFile); +} + +std::ostream& operator<<(std::ostream& o, WasmSplitOptions::Mode& mode) { + switch (mode) { + case WasmSplitOptions::Mode::Split: + o << "split"; + break; + case WasmSplitOptions::Mode::Instrument: + o << "instrument"; + break; + case WasmSplitOptions::Mode::MergeProfiles: + o << "merge-profiles"; + break; + } + return o; +} + +} // anonymous namespace + +WasmSplitOptions::WasmSplitOptions() + : ToolOptions("wasm-split", + "Split a module into a primary module and a secondary " + "module, or instrument a module to gather a profile that " + "can inform future splitting, or manage such profiles. Options " + "that are only accepted in particular modes are marked with " + "the accepted \"[<modes>]\" in their descriptions.") { + (*this) + .add("--split", + "", + "Split an input module into two output modules. The default mode.", + Options::Arguments::Zero, + [&](Options* o, const std::string& arugment) { mode = Mode::Split; }) + .add( + "--instrument", + "", + "Instrument an input module to allow it to generate a profile that can" + " be used to guide splitting.", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { mode = Mode::Instrument; }) + .add("--merge-profiles", + "", + "Merge multiple profiles for the same module into a single profile.", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { + mode = Mode::MergeProfiles; + }) + .add( + "--profile", + "", + "The profile to use to guide splitting.", + {Mode::Split}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { profileFile = argument; }) + .add("--keep-funcs", + "", + "Comma-separated list of functions to keep in the primary module, " + "regardless of any profile. " + "You can also pass a file with a list of functions separated by new " + "lines. " + "To do this, prepend @ before filename (--keep-funcs @myfile)", + {Mode::Split}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { + keepFuncs = parseNameList(argument); + }) + .add("--split-funcs", + "", + "Comma-separated list of functions to split into the secondary " + "module, regardless of any profile. If there is no profile, then " + "this defaults to all functions defined in the module. " + "You can also pass a file with a list of functions separated by new " + "lines. " + "To do this, prepend @ before filename (--split-funcs @myfile)", + {Mode::Split}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { + splitFuncs = parseNameList(argument); + }) + .add("--primary-output", + "-o1", + "Output file for the primary module.", + {Mode::Split}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { + primaryOutput = argument; + }) + .add("--secondary-output", + "-o2", + "Output file for the secondary module.", + {Mode::Split}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { + secondaryOutput = argument; + }) + .add("--symbolmap", + "", + "Write a symbol map file for each of the output modules.", + {Mode::Split}, + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { symbolMap = true; }) + .add( + "--placeholdermap", + "", + "Write a file mapping placeholder indices to the function names.", + {Mode::Split}, + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { placeholderMap = true; }) + .add("--import-namespace", + "", + "The namespace from which to import objects from the primary " + "module into the secondary module.", + {Mode::Split}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { + importNamespace = argument; + }) + .add("--placeholder-namespace", + "", + "The namespace from which to import placeholder functions into " + "the primary module.", + {Mode::Split}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { + placeholderNamespace = argument; + }) + .add( + "--export-prefix", + "", + "An identifying prefix to prepend to new export names created " + "by module splitting.", + {Mode::Split}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { exportPrefix = argument; }) + .add("--profile-export", + "", + "The export name of the function the embedder calls to write the " + "profile into memory. Defaults to `__write_profile`.", + {Mode::Instrument}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { + profileExport = argument; + }) + .add( + "--emit-module-names", + "", + "Emit module names, even if not emitting the rest of the names section. " + "Can help differentiate the modules in stack traces. This option will be " + "removed once simpler ways of naming modules are widely available. See " + "https://bugs.chromium.org/p/v8/issues/detail?id=11808.", + {Mode::Split, Mode::Instrument}, + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { emitModuleNames = true; }) + .add("--initial-table", + "", + "A hack to ensure the split and instrumented modules have the same " + "table size when using Emscripten's SPLIT_MODULE mode with dynamic " + "linking. TODO: Figure out a more elegant solution for that use " + "case and remove this.", + {Mode::Split, Mode::Instrument}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { + initialTableSize = std::stoi(argument); + }) + .add("--emit-text", + "-S", + "Emit text instead of binary for the output file or files.", + {Mode::Split, Mode::Instrument}, + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { emitBinary = false; }) + .add("--debuginfo", + "-g", + "Emit names section in wasm binary (or full debuginfo in wast)", + {Mode::Split, Mode::Instrument}, + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { + passOptions.debugInfo = true; + }) + .add("--output", + "-o", + "Output file.", + {Mode::Instrument, Mode::MergeProfiles}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { output = argument; }) + .add("--verbose", + "-v", + "Verbose output mode. Prints the functions that will be kept " + "and split out when splitting a module.", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { + verbose = true; + quiet = false; + }) + .add_positional("INFILES", + Options::Arguments::N, + [&](Options* o, const std::string& argument) { + inputFiles.push_back(argument); + }); +} + +WasmSplitOptions& WasmSplitOptions::add(const std::string& longName, + const std::string& shortName, + const std::string& description, + std::vector<Mode>&& modes, + Arguments arguments, + const Action& action) { + // Insert the valid modes at the beginning of the description. + std::stringstream desc; + if (modes.size()) { + desc << '['; + std::string sep = ""; + for (Mode m : modes) { + validOptions[static_cast<unsigned>(m)].insert(longName); + desc << sep << m; + sep = ", "; + } + desc << "] "; + } + desc << description; + ToolOptions::add( + longName, + shortName, + desc.str(), + arguments, + [&, action, longName](Options* o, const std::string& argument) { + usedOptions.push_back(longName); + action(o, argument); + }); + return *this; +} + +WasmSplitOptions& WasmSplitOptions::add(const std::string& longName, + const std::string& shortName, + const std::string& description, + Arguments arguments, + const Action& action) { + // Add an option valid in all modes. + for (unsigned i = 0; i < NumModes; ++i) { + validOptions[i].insert(longName); + } + return add(longName, shortName, description, {}, arguments, action); +} + +bool WasmSplitOptions::validate() { + bool valid = true; + auto fail = [&](auto msg) { + std::cerr << "error: " << msg << "\n"; + valid = false; + }; + + // Validate the positional arguments. + if (inputFiles.size() == 0) { + fail("no input file"); + } + switch (mode) { + case Mode::Split: + case Mode::Instrument: + if (inputFiles.size() > 1) { + fail("Cannot have more than one input file."); + } + break; + case Mode::MergeProfiles: + // Any number >= 1 allowed. + break; + } + + // Validate that all used options are allowed in the current mode. + for (std::string& opt : usedOptions) { + if (!validOptions[static_cast<unsigned>(mode)].count(opt)) { + std::stringstream msg; + msg << "Option " << opt << " cannot be used in " << mode << " mode."; + fail(msg.str()); + } + } + + if (mode == Mode::Split) { + std::vector<Name> impossible; + std::set_intersection(keepFuncs.begin(), + keepFuncs.end(), + splitFuncs.begin(), + splitFuncs.end(), + std::inserter(impossible, impossible.end())); + for (auto& func : impossible) { + fail(std::string("Cannot both keep and split out function ") + + func.c_str()); + } + } + + return valid; +} + +void WasmSplitOptions::parse(int argc, const char* argv[]) { + ToolOptions::parse(argc, argv); + // Since --quiet is defined in ToolOptions but --verbose is defined here, + // --quiet doesn't know to unset --verbose. Fix it up here. + if (quiet && verbose) { + verbose = false; + } +} + +} // namespace wasm |