summaryrefslogtreecommitdiff
path: root/src/tools/wasm-split/split-options.cpp
diff options
context:
space:
mode:
authorThomas Lively <7121787+tlively@users.noreply.github.com>2021-09-03 08:24:08 -0700
committerGitHub <noreply@github.com>2021-09-03 08:24:08 -0700
commit548d1971c3c844e8dab8c0da4e97aa5c339937df (patch)
tree188cec7d93b7743f987c101e6a8d8999d3c1d62d /src/tools/wasm-split/split-options.cpp
parente84980dffb62d672c991960a701a3c7de8f8aa74 (diff)
downloadbinaryen-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.cpp356
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