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.cpp367
1 files changed, 367 insertions, 0 deletions
diff --git a/src/tools/wasm-split.cpp b/src/tools/wasm-split.cpp
new file mode 100644
index 000000000..5f6207e9a
--- /dev/null
+++ b/src/tools/wasm-split.cpp
@@ -0,0 +1,367 @@
+/*
+ * 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 "support/name.h"
+#include "support/utilities.h"
+#include "tool-options.h"
+#include "wasm-io.h"
+#include "wasm-validator.h"
+#include <sstream>
+
+using namespace wasm;
+
+namespace {
+
+const std::string DEFAULT_PROFILE_EXPORT("__write_profile");
+
+std::set<Name> parseNameList(const std::string& list) {
+ std::set<Name> names;
+ std::istringstream stream(list);
+ for (std::string name; std::getline(stream, name, ',');) {
+ names.insert(name);
+ }
+ return names;
+}
+
+struct WasmSplitOptions : ToolOptions {
+ bool verbose = false;
+
+ bool instrument = false;
+
+ std::string profileFile;
+ std::string profileExport = DEFAULT_PROFILE_EXPORT;
+
+ std::set<Name> keepFuncs;
+ std::set<Name> splitFuncs;
+
+ std::string input;
+ std::string output;
+ std::string primaryOutput;
+ std::string secondaryOutput;
+
+ std::string importNamespace;
+ std::string placeholderNamespace;
+ std::string exportPrefix;
+
+ WasmSplitOptions();
+ 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.") {
+ (*this)
+ .add("--instrument",
+ "",
+ "Instrument the module to generate a profile that can be used to "
+ "guide splitting",
+ Options::Arguments::Zero,
+ [&](Options* o, const std::string& argument) { instrument = true; })
+ .add(
+ "--profile",
+ "",
+ "The profile to use to guide splitting. May not be used with "
+ "--instrument.",
+ Options::Arguments::One,
+ [&](Options* o, const std::string& argument) { profileFile = argument; })
+ .add("--profile-export",
+ "",
+ "The export name of the function the embedder calls to write the "
+ "profile into memory. Defaults to `__write_profile`. Must be used "
+ "with --instrument.",
+ Options::Arguments::One,
+ [&](Options* o, const std::string& argument) {
+ profileExport = argument;
+ })
+ .add("--keep-funcs",
+ "",
+ "Comma-separated list of functions to keep in the primary module, "
+ "regardless of any profile.",
+ 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.",
+ Options::Arguments::One,
+ [&](Options* o, const std::string& argument) {
+ splitFuncs = parseNameList(argument);
+ })
+ .add("--output",
+ "-o",
+ "Output file. Only usable with --instrument.",
+ Options::Arguments::One,
+ [&](Options* o, const std::string& argument) { output = argument; })
+ .add("--primary-output",
+ "-o1",
+ "Output file for the primary module. Not usable with --instrument.",
+ Options::Arguments::One,
+ [&](Options* o, const std::string& argument) {
+ primaryOutput = argument;
+ })
+ .add("--secondary-output",
+ "-o2",
+ "Output file for the secondary module. Not usable with --instrument.",
+ Options::Arguments::One,
+ [&](Options* o, const std::string& argument) {
+ secondaryOutput = argument;
+ })
+ .add("--import-namespace",
+ "",
+ "The namespace from which to import objects from the primary "
+ "module into the secondary module.",
+ 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.",
+ 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.",
+ Options::Arguments::One,
+ [&](Options* o, const std::string& argument) { exportPrefix = 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(
+ "INFILE",
+ Options::Arguments::One,
+ [&](Options* o, const std::string& argument) { input = argument; });
+}
+
+bool WasmSplitOptions::validate() {
+ bool valid = true;
+ auto fail = [&](auto msg) {
+ std::cerr << "error: " << msg << "\n";
+ valid = false;
+ };
+
+ if (!input.size()) {
+ fail("no input file");
+ }
+ if (instrument) {
+ using Opt = std::pair<const std::string&, const std::string>;
+ for (auto& opt : {Opt{profileFile, "--profile"},
+ Opt{primaryOutput, "primary output"},
+ Opt{secondaryOutput, "secondary output"},
+ Opt{importNamespace, "--import-namespace"},
+ Opt{placeholderNamespace, "--placeholder-namespace"},
+ Opt{exportPrefix, "--export-prefix"}}) {
+ if (opt.first.size()) {
+ fail(opt.second + " cannot be used with --instrument");
+ }
+ }
+ if (keepFuncs.size()) {
+ fail("--keep-funcs cannot be used with --instrument");
+ }
+ if (splitFuncs.size()) {
+ fail("--split-funcs cannot be used with --instrument");
+ }
+ } else {
+ if (output.size()) {
+ fail(
+ "must provide separate primary and secondary output with -o1 and -o2");
+ }
+ if (profileExport != DEFAULT_PROFILE_EXPORT) {
+ fail("--profile-export must be used with --instrument");
+ }
+ }
+
+ 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) {
+ ModuleReader reader;
+ reader.setProfile(options.profile);
+ try {
+ reader.read(options.input, 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)";
+ }
+ options.applyFeatures(wasm);
+}
+
+void instrumentModule(Module& wasm, const WasmSplitOptions& options) {
+ Fatal() << "TODO: implement instrumentation\n";
+}
+
+void splitModule(Module& wasm, const WasmSplitOptions& options) {
+ std::set<Name> keepFuncs;
+
+ if (options.profileFile.size()) {
+ // Use the profile to initialize `keepFuncs`
+ Fatal() << "TODO: implement reading profiles\n";
+ }
+
+ // 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;
+ }
+ std::unique_ptr<Module> secondary =
+ ModuleSplitting::splitFunctions(wasm, config);
+
+ // Write the output modules
+ ModuleWriter writer;
+ writer.setBinary(true);
+ writer.write(wasm, options.primaryOutput);
+ writer.write(*secondary, options.secondaryOutput);
+}
+
+} // anonymous namespace
+
+int main(int argc, const char* argv[]) {
+ WasmSplitOptions options;
+ options.parse(argc, argv);
+
+ if (!options.validate()) {
+ Fatal() << "Invalid command line arguments";
+ }
+
+ Module wasm;
+ parseInput(wasm, options);
+
+ if (options.passOptions.validate && !WasmValidator().validate(wasm)) {
+ Fatal() << "error validating input";
+ }
+
+ if (options.instrument) {
+ instrumentModule(wasm, options);
+ } else {
+ splitModule(wasm, options);
+ }
+}