summaryrefslogtreecommitdiff
path: root/src/tools/wasm-split.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/wasm-split.cpp')
-rw-r--r--src/tools/wasm-split.cpp969
1 files changed, 0 insertions, 969 deletions
diff --git a/src/tools/wasm-split.cpp b/src/tools/wasm-split.cpp
deleted file mode 100644
index 83c170abe..000000000
--- a/src/tools/wasm-split.cpp
+++ /dev/null
@@ -1,969 +0,0 @@
-/*
- * Copyright 2020 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.
- */
-
-// wasm-split: Split a module in two or instrument a module to inform future
-// splitting.
-
-#include "ir/module-splitting.h"
-#include "ir/module-utils.h"
-#include "ir/names.h"
-#include "pass.h"
-#include "support/file.h"
-#include "support/name.h"
-#include "support/path.h"
-#include "support/utilities.h"
-#include "tool-options.h"
-#include "wasm-binary.h"
-#include "wasm-builder.h"
-#include "wasm-io.h"
-#include "wasm-type.h"
-#include "wasm-validator.h"
-#include <sstream>
-
-using namespace wasm;
-
-namespace {
-
-const std::string DEFAULT_PROFILE_EXPORT("__write_profile");
-
-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);
-}
-
-struct WasmSplitOptions : ToolOptions {
- enum class Mode : unsigned {
- Split,
- Instrument,
- MergeProfiles,
- };
- Mode mode = Mode::Split;
- constexpr static size_t NumModes =
- static_cast<unsigned>(Mode::MergeProfiles) + 1;
-
- bool verbose = false;
- bool emitBinary = true;
- bool symbolMap = false;
- bool placeholderMap = false;
-
- // TODO: Remove this. See the comment in wasm-binary.h.
- bool emitModuleNames = false;
-
- std::string profileFile;
- std::string profileExport = DEFAULT_PROFILE_EXPORT;
-
- std::set<Name> keepFuncs;
- std::set<Name> splitFuncs;
-
- std::vector<std::string> inputFiles;
- std::string output;
- std::string primaryOutput;
- std::string secondaryOutput;
-
- std::string importNamespace;
- std::string placeholderNamespace;
- std::string exportPrefix;
-
- // 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.
- int initialTableSize = -1;
-
- // The options that are valid for each mode.
- std::array<std::unordered_set<std::string>, NumModes> validOptions;
- std::vector<std::string> usedOptions;
-
- WasmSplitOptions();
- WasmSplitOptions& add(const std::string& longName,
- const std::string& shortName,
- const std::string& description,
- std::vector<Mode>&& modes,
- Arguments arguments,
- const Action& action);
- WasmSplitOptions& add(const std::string& longName,
- const std::string& shortName,
- const std::string& description,
- Arguments arguments,
- const Action& action);
- bool validate();
- void parse(int argc, const char* argv[]);
-};
-
-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);
- });
-}
-
-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;
-}
-
-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;
- }
-}
-
-void parseInput(Module& wasm, const WasmSplitOptions& options) {
- options.applyFeatures(wasm);
- ModuleReader reader;
- reader.setProfile(options.profile);
- try {
- reader.read(options.inputFiles[0], wasm);
- } catch (ParseException& p) {
- p.dump(std::cerr);
- std::cerr << '\n';
- Fatal() << "error parsing wasm";
- } catch (std::bad_alloc&) {
- Fatal() << "error building module, std::bad_alloc (possibly invalid "
- "request for silly amounts of memory)";
- }
-
- if (options.passOptions.validate && !WasmValidator().validate(wasm)) {
- Fatal() << "error validating input";
- }
-}
-
-// Add a global monotonic counter and a timestamp global for each function, code
-// at the beginning of each function to set its timestamp, and a new exported
-// function for dumping the profile data.
-struct Instrumenter : public Pass {
- PassRunner* runner = nullptr;
- Module* wasm = nullptr;
-
- const std::string& profileExport;
- uint64_t moduleHash;
-
- Name counterGlobal;
- std::vector<Name> functionGlobals;
-
- Instrumenter(const std::string& profileExport, uint64_t moduleHash);
-
- void run(PassRunner* runner, Module* wasm) override;
- void addGlobals();
- void instrumentFuncs();
- void addProfileExport();
-};
-
-Instrumenter::Instrumenter(const std::string& profileExport,
- uint64_t moduleHash)
- : profileExport(profileExport), moduleHash(moduleHash) {}
-
-void Instrumenter::run(PassRunner* runner, Module* wasm) {
- this->runner = runner;
- this->wasm = wasm;
- addGlobals();
- instrumentFuncs();
- addProfileExport();
-}
-
-void Instrumenter::addGlobals() {
- // Create fresh global names (over-reserves, but that's ok)
- counterGlobal = Names::getValidGlobalName(*wasm, "monotonic_counter");
- functionGlobals.reserve(wasm->functions.size());
- ModuleUtils::iterDefinedFunctions(*wasm, [&](Function* func) {
- functionGlobals.push_back(Names::getValidGlobalName(
- *wasm, std::string(func->name.c_str()) + "_timestamp"));
- });
-
- // Create and add new globals
- auto addGlobal = [&](Name name) {
- auto global = Builder::makeGlobal(
- name,
- Type::i32,
- Builder(*wasm).makeConst(Literal::makeZero(Type::i32)),
- Builder::Mutable);
- global->hasExplicitName = true;
- wasm->addGlobal(std::move(global));
- };
- addGlobal(counterGlobal);
- for (auto& name : functionGlobals) {
- addGlobal(name);
- }
-}
-
-void Instrumenter::instrumentFuncs() {
- // Inject the following code at the beginning of each function to advance the
- // monotonic counter and set the function's timestamp if it hasn't already
- // been set.
- //
- // (if (i32.eqz (global.get $timestamp))
- // (block
- // (global.set $monotonic_counter
- // (i32.add
- // (global.get $monotonic_counter)
- // (i32.const 1)
- // )
- // )
- // (global.set $timestamp
- // (global.get $monotonic_counter)
- // )
- // )
- // )
- Builder builder(*wasm);
- auto globalIt = functionGlobals.begin();
- ModuleUtils::iterDefinedFunctions(*wasm, [&](Function* func) {
- func->body = builder.makeSequence(
- builder.makeIf(
- builder.makeUnary(EqZInt32,
- builder.makeGlobalGet(*globalIt, Type::i32)),
- builder.makeSequence(
- builder.makeGlobalSet(
- counterGlobal,
- builder.makeBinary(AddInt32,
- builder.makeGlobalGet(counterGlobal, Type::i32),
- builder.makeConst(Literal::makeOne(Type::i32)))),
- builder.makeGlobalSet(
- *globalIt, builder.makeGlobalGet(counterGlobal, Type::i32)))),
- func->body,
- func->body->type);
- ++globalIt;
- });
-}
-
-// wasm-split profile format:
-//
-// The wasm-split profile is a binary format designed to be simple to produce
-// and consume. It is comprised of:
-//
-// 1. An 8-byte module hash
-//
-// 2. A 4-byte timestamp for each defined function
-//
-// The module hash is meant to guard against bugs where the module that was
-// instrumented and the module that is being split are different. The timestamps
-// are non-zero for functions that were called during the instrumented run and 0
-// otherwise. Functions with smaller non-zero timestamps were called earlier in
-// the instrumented run than funtions with larger timestamps.
-
-void Instrumenter::addProfileExport() {
- // Create and export a function to dump the profile into a given memory
- // buffer. The function takes the available address and buffer size as
- // arguments and returns the total size of the profile. It only actually
- // writes the profile if the given space is sufficient to hold it.
- auto name = Names::getValidFunctionName(*wasm, profileExport);
- auto writeProfile = Builder::makeFunction(
- name, Signature({Type::i32, Type::i32}, Type::i32), {});
- writeProfile->hasExplicitName = true;
- writeProfile->setLocalName(0, "addr");
- writeProfile->setLocalName(1, "size");
-
- // Calculate the size of the profile:
- // 8 bytes module hash +
- // 4 bytes for the timestamp for each function
- const size_t profileSize = 8 + 4 * functionGlobals.size();
-
- // Create the function body
- Builder builder(*wasm);
- auto getAddr = [&]() { return builder.makeLocalGet(0, Type::i32); };
- auto getSize = [&]() { return builder.makeLocalGet(1, Type::i32); };
- auto hashConst = [&]() { return builder.makeConst(int64_t(moduleHash)); };
- auto profileSizeConst = [&]() {
- return builder.makeConst(int32_t(profileSize));
- };
-
- // Write the hash followed by all the time stamps
- Expression* writeData =
- builder.makeStore(8, 0, 1, getAddr(), hashConst(), Type::i64);
-
- uint32_t offset = 8;
- for (const auto& global : functionGlobals) {
- writeData = builder.blockify(
- writeData,
- builder.makeStore(4,
- offset,
- 1,
- getAddr(),
- builder.makeGlobalGet(global, Type::i32),
- Type::i32));
- offset += 4;
- }
-
- writeProfile->body = builder.makeSequence(
- builder.makeIf(builder.makeBinary(GeUInt32, getSize(), profileSizeConst()),
- writeData),
- profileSizeConst());
-
- // Create an export for the function
- wasm->addFunction(std::move(writeProfile));
- wasm->addExport(
- Builder::makeExport(profileExport, name, ExternalKind::Function));
-
- // Also make sure there is a memory with enough pages to write into
- size_t pages = (profileSize + Memory::kPageSize - 1) / Memory::kPageSize;
- if (!wasm->memory.exists) {
- wasm->memory.exists = true;
- wasm->memory.initial = pages;
- wasm->memory.max = pages;
- } else if (wasm->memory.initial < pages) {
- wasm->memory.initial = pages;
- if (wasm->memory.max < pages) {
- wasm->memory.max = pages;
- }
- }
-
- // TODO: export the memory if it is not already exported.
-}
-
-uint64_t hashFile(const std::string& filename) {
- auto contents(read_file<std::vector<char>>(filename, Flags::Binary));
- size_t digest = 0;
- // Don't use `hash` or `rehash` - they aren't deterministic between executions
- for (char c : contents) {
- hash_combine(digest, c);
- }
- return uint64_t(digest);
-}
-
-void adjustTableSize(Module& wasm, int initialSize) {
- if (initialSize < 0) {
- return;
- }
- if (wasm.tables.empty()) {
- Fatal() << "--initial-table used but there is no table";
- }
-
- auto& table = wasm.tables.front();
-
- if ((uint64_t)initialSize < table->initial) {
- Fatal() << "Specified initial table size too small, should be at least "
- << table->initial;
- }
- if ((uint64_t)initialSize > table->max) {
- Fatal() << "Specified initial table size larger than max table size "
- << table->max;
- }
- table->initial = initialSize;
-}
-
-void writeModule(Module& wasm,
- std::string filename,
- const WasmSplitOptions& options) {
- ModuleWriter writer;
- writer.setBinary(options.emitBinary);
- writer.setDebugInfo(options.passOptions.debugInfo);
- if (options.emitModuleNames) {
- writer.setEmitModuleName(true);
- }
- writer.write(wasm, filename);
-}
-
-void instrumentModule(const WasmSplitOptions& options) {
- Module wasm;
- parseInput(wasm, options);
-
- // Check that the profile export name is not already taken
- if (wasm.getExportOrNull(options.profileExport) != nullptr) {
- Fatal() << "error: Export " << options.profileExport << " already exists.";
- }
-
- uint64_t moduleHash = hashFile(options.inputFiles[0]);
- PassRunner runner(&wasm, options.passOptions);
- Instrumenter(options.profileExport, moduleHash).run(&runner, &wasm);
-
- adjustTableSize(wasm, options.initialTableSize);
-
- // Write the output modules
- writeModule(wasm, options.output, options);
-}
-
-struct ProfileData {
- uint64_t hash;
- std::vector<size_t> timestamps;
-};
-
-// See "wasm-split profile format" above for more information.
-ProfileData readProfile(const std::string& file) {
- auto profileData = read_file<std::vector<char>>(file, Flags::Binary);
- size_t i = 0;
- auto readi32 = [&]() {
- if (i + 4 > profileData.size()) {
- Fatal() << "Unexpected end of profile data in " << file;
- }
- uint32_t i32 = 0;
- i32 |= uint32_t(uint8_t(profileData[i++]));
- i32 |= uint32_t(uint8_t(profileData[i++])) << 8;
- i32 |= uint32_t(uint8_t(profileData[i++])) << 16;
- i32 |= uint32_t(uint8_t(profileData[i++])) << 24;
- return i32;
- };
-
- uint64_t hash = readi32();
- hash |= uint64_t(readi32()) << 32;
-
- std::vector<size_t> timestamps;
- while (i < profileData.size()) {
- timestamps.push_back(readi32());
- }
-
- return {hash, timestamps};
-}
-
-void writeSymbolMap(Module& wasm, std::string filename) {
- PassOptions options;
- options.arguments["symbolmap"] = filename;
- PassRunner runner(&wasm, options);
- runner.add("symbolmap");
- runner.run();
-}
-
-void writePlaceholderMap(const std::map<size_t, Name> placeholderMap,
- std::string filename) {
- Output output(filename, Flags::Text);
- auto& o = output.getStream();
- for (auto pair : placeholderMap) {
- o << pair.first << ':' << pair.second << '\n';
- }
-}
-
-void splitModule(const WasmSplitOptions& options) {
- Module wasm;
- parseInput(wasm, options);
-
- std::set<Name> keepFuncs;
-
- if (options.profileFile.size()) {
- // Use the profile to initialize `keepFuncs`.
- uint64_t hash = hashFile(options.inputFiles[0]);
- ProfileData profile = readProfile(options.profileFile);
- if (profile.hash != hash) {
- Fatal() << "error: checksum in profile does not match module checksum. "
- << "The split module must be the original module that was "
- << "instrumented to generate the profile.";
- }
- size_t i = 0;
- ModuleUtils::iterDefinedFunctions(wasm, [&](Function* func) {
- if (i >= profile.timestamps.size()) {
- Fatal() << "Unexpected end of profile data";
- }
- if (profile.timestamps[i++] > 0) {
- keepFuncs.insert(func->name);
- }
- });
- if (i != profile.timestamps.size()) {
- Fatal() << "Unexpected extra profile data";
- }
- }
-
- // Add in the functions specified with --keep-funcs
- for (auto& func : options.keepFuncs) {
- if (!options.quiet && wasm.getFunctionOrNull(func) == nullptr) {
- std::cerr << "warning: function " << func << " does not exist\n";
- }
- keepFuncs.insert(func);
- }
-
- // Remove the functions specified with --remove-funcs
- for (auto& func : options.splitFuncs) {
- auto* function = wasm.getFunctionOrNull(func);
- if (!options.quiet && function == nullptr) {
- std::cerr << "warning: function " << func << " does not exist\n";
- }
- if (function && function->imported()) {
- if (!options.quiet) {
- std::cerr << "warning: cannot split out imported function " << func
- << "\n";
- }
- } else {
- keepFuncs.erase(func);
- }
- }
-
- if (!options.quiet && keepFuncs.size() == 0) {
- std::cerr << "warning: not keeping any functions in the primary module\n";
- }
-
- // If warnings are enabled, check that any functions are being split out.
- if (!options.quiet) {
- std::set<Name> splitFuncs;
- ModuleUtils::iterDefinedFunctions(wasm, [&](Function* func) {
- if (keepFuncs.count(func->name) == 0) {
- splitFuncs.insert(func->name);
- }
- });
-
- if (splitFuncs.size() == 0) {
- std::cerr
- << "warning: not splitting any functions out to the secondary module\n";
- }
-
- // Dump the kept and split functions if we are verbose
- if (options.verbose) {
- auto printCommaSeparated = [&](auto funcs) {
- for (auto it = funcs.begin(); it != funcs.end(); ++it) {
- if (it != funcs.begin()) {
- std::cout << ", ";
- }
- std::cout << *it;
- }
- };
-
- std::cout << "Keeping functions: ";
- printCommaSeparated(keepFuncs);
- std::cout << "\n";
-
- std::cout << "Splitting out functions: ";
- printCommaSeparated(splitFuncs);
- std::cout << "\n";
- }
- }
-
- // Actually perform the splitting
- ModuleSplitting::Config config;
- config.primaryFuncs = std::move(keepFuncs);
- if (options.importNamespace.size()) {
- config.importNamespace = options.importNamespace;
- }
- if (options.placeholderNamespace.size()) {
- config.placeholderNamespace = options.placeholderNamespace;
- }
- if (options.exportPrefix.size()) {
- config.newExportPrefix = options.exportPrefix;
- }
- config.minimizeNewExportNames = !options.passOptions.debugInfo;
- auto splitResults = ModuleSplitting::splitFunctions(wasm, config);
- auto& secondary = splitResults.secondary;
-
- adjustTableSize(wasm, options.initialTableSize);
- adjustTableSize(*secondary, options.initialTableSize);
-
- if (options.symbolMap) {
- writeSymbolMap(wasm, options.primaryOutput + ".symbols");
- writeSymbolMap(*secondary, options.secondaryOutput + ".symbols");
- }
-
- if (options.placeholderMap) {
- writePlaceholderMap(splitResults.placeholderMap,
- options.primaryOutput + ".placeholders");
- }
-
- // Set the names of the split modules. This can help differentiate them in
- // stack traces.
- if (options.emitModuleNames) {
- if (!wasm.name) {
- wasm.name = Path::getBaseName(options.primaryOutput);
- }
- secondary->name = Path::getBaseName(options.secondaryOutput);
- }
-
- // write the output modules
- writeModule(wasm, options.primaryOutput, options);
- writeModule(*secondary, options.secondaryOutput, options);
-}
-
-void mergeProfiles(const WasmSplitOptions& options) {
- // Read the initial profile. We will merge other profiles into this one.
- ProfileData data = readProfile(options.inputFiles[0]);
-
- // In verbose mode, we want to find profiles that don't contribute to the
- // merged profile. To do that, keep track of how many profiles each function
- // appears in. If any profile contains only functions that appear in multiple
- // profiles, it could be dropped.
- std::vector<size_t> numProfiles;
- if (options.verbose) {
- numProfiles.resize(data.timestamps.size());
- for (size_t t = 0; t < data.timestamps.size(); ++t) {
- if (data.timestamps[t]) {
- numProfiles[t] = 1;
- }
- }
- }
-
- // Read all the other profiles, taking the minimum nonzero timestamp for each
- // function.
- for (size_t i = 1; i < options.inputFiles.size(); ++i) {
- ProfileData newData = readProfile(options.inputFiles[i]);
- if (newData.hash != data.hash) {
- Fatal() << "Checksum in profile " << options.inputFiles[i]
- << " does not match hash in profile " << options.inputFiles[0];
- }
- if (newData.timestamps.size() != data.timestamps.size()) {
- Fatal() << "Profile " << options.inputFiles[i]
- << " incompatible with profile " << options.inputFiles[0];
- }
- for (size_t t = 0; t < data.timestamps.size(); ++t) {
- if (data.timestamps[t] && newData.timestamps[t]) {
- data.timestamps[t] =
- std::min(data.timestamps[t], newData.timestamps[t]);
- } else if (newData.timestamps[t]) {
- data.timestamps[t] = newData.timestamps[t];
- }
- if (options.verbose && newData.timestamps[t]) {
- ++numProfiles[t];
- }
- }
- }
-
- // Check for useless profiles.
- if (options.verbose) {
- for (const auto& file : options.inputFiles) {
- bool useless = true;
- ProfileData newData = readProfile(file);
- for (size_t t = 0; t < newData.timestamps.size(); ++t) {
- if (newData.timestamps[t] && numProfiles[t] == 1) {
- useless = false;
- break;
- }
- }
- if (useless) {
- std::cout << "Profile " << file
- << " only includes functions included in other profiles.\n";
- }
- }
- }
-
- // Write the combined profile.
- BufferWithRandomAccess buffer;
- buffer << data.hash;
- for (size_t t = 0; t < data.timestamps.size(); ++t) {
- buffer << uint32_t(data.timestamps[t]);
- }
- Output out(options.output, Flags::Binary);
- buffer.writeTo(out.getStream());
-}
-
-} // anonymous namespace
-
-int main(int argc, const char* argv[]) {
- WasmSplitOptions options;
- options.parse(argc, argv);
-
- if (!options.validate()) {
- Fatal() << "Invalid command line arguments";
- }
-
- switch (options.mode) {
- case WasmSplitOptions::Mode::Split:
- splitModule(options);
- break;
- case WasmSplitOptions::Mode::Instrument:
- instrumentModule(options);
- break;
- case WasmSplitOptions::Mode::MergeProfiles:
- mergeProfiles(options);
- break;
- }
-}