summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--src/tools/tool-options.h2
-rw-r--r--src/tools/wasm-split.cpp367
-rw-r--r--test/lit/wasm-split/basic.wast141
-rw-r--r--test/lit/wasm-split/invalid-options.wast64
-rw-r--r--test/lit/wasm-split/verbose.wast14
6 files changed, 588 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5af62c9c8..708eec54f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -336,6 +336,7 @@ binaryen_add_executable(wasm-as src/tools/wasm-as.cpp)
binaryen_add_executable(wasm-dis src/tools/wasm-dis.cpp)
binaryen_add_executable(wasm-ctor-eval src/tools/wasm-ctor-eval.cpp)
binaryen_add_executable(wasm-reduce src/tools/wasm-reduce.cpp)
+binaryen_add_executable(wasm-split src/tools/wasm-split.cpp)
# binaryen.js
diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h
index 83d5b38d7..4b084e191 100644
--- a/src/tools/tool-options.h
+++ b/src/tools/tool-options.h
@@ -140,7 +140,7 @@ struct ToolOptions : public Options {
return *this;
}
- void applyFeatures(Module& module) {
+ void applyFeatures(Module& module) const {
if (hasFeatureOptions) {
if (!detectFeatures && module.hasFeaturesSection) {
FeatureSet optionsFeatures = FeatureSet::MVP;
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);
+ }
+}
diff --git a/test/lit/wasm-split/basic.wast b/test/lit/wasm-split/basic.wast
new file mode 100644
index 000000000..d282b0bd6
--- /dev/null
+++ b/test/lit/wasm-split/basic.wast
@@ -0,0 +1,141 @@
+;; RUN: wasm-split %s --export-prefix='%' -o1 %t.none.1.wasm -o2 %t.none.2.wasm -v 2>&1 \
+;; RUN: | filecheck %s --check-prefix KEEP-NONE
+;; RUN: wasm-dis %t.none.1.wasm | filecheck %s --check-prefix KEEP-NONE-PRIMARY
+;; RUN: wasm-dis %t.none.2.wasm | filecheck %s --check-prefix KEEP-NONE-SECONDARY
+
+;; RUN: wasm-split %s --export-prefix='%' -o1 %t.foo.1.wasm -o2 %t.foo.2.wasm --keep-funcs=foo -v 2>&1 \
+;; RUN: | filecheck %s --check-prefix KEEP-FOO
+;; RUN: wasm-dis %t.foo.1.wasm | filecheck %s --check-prefix KEEP-FOO-PRIMARY
+;; RUN: wasm-dis %t.foo.2.wasm | filecheck %s --check-prefix KEEP-FOO-SECONDARY
+
+;; RUN: wasm-split %s --export-prefix='%' -o1 %t.bar.1.wasm -o2 %t.bar.2.wasm --keep-funcs=bar -v 2>&1 \
+;; RUN: | filecheck %s --check-prefix KEEP-BAR
+;; RUN: wasm-dis %t.bar.1.wasm | filecheck %s --check-prefix KEEP-BAR-PRIMARY
+;; RUN: wasm-dis %t.bar.2.wasm | filecheck %s --check-prefix KEEP-BAR-SECONDARY
+
+;; RUN: wasm-split %s --export-prefix='%' -o1 %t.both.1.wasm -o2 %t.both.2.wasm --keep-funcs=foo,bar -v 2>&1 \
+;; RUN: | filecheck %s --check-prefix KEEP-BOTH
+;; RUN: wasm-dis %t.both.1.wasm | filecheck %s --check-prefix KEEP-BOTH-PRIMARY
+;; RUN: wasm-dis %t.both.2.wasm | filecheck %s --check-prefix KEEP-BOTH-SECONDARY
+
+(module
+ (table $table 1 1 funcref)
+ (elem (i32.const 0) $foo)
+ (func $foo (param i32) (result i32)
+ (call $bar (i32.const 0))
+ )
+ (func $bar (param i32) (result i32)
+ (call $foo (i32.const 1))
+ )
+)
+
+;; KEEP-NONE: warning: not keeping any functions in the primary module
+
+;; KEEP-NONE-PRIMARY: (module
+;; KEEP-NONE-PRIMARY-NEXT: (type $i32_=>_i32 (func (param i32) (result i32)))
+;; KEEP-NONE-PRIMARY-NEXT: (import "placeholder" "0" (func $fimport$0 (param i32) (result i32)))
+;; KEEP-NONE-PRIMARY-NEXT: (table $0 1 1 funcref)
+;; KEEP-NONE-PRIMARY-NEXT: (elem (i32.const 0) $fimport$0)
+;; KEEP-NONE-PRIMARY-NEXT: (export "%table" (table $0))
+;; KEEP-NONE-PRIMARY-NEXT: )
+
+;; KEEP-NONE-SECONDARY: (module
+;; KEEP-NONE-SECONDARY-NEXT: (type $i32_=>_i32 (func (param i32) (result i32)))
+;; KEEP-NONE-SECONDARY-NEXT: (import "primary" "%table" (table $timport$0 1 1 funcref))
+;; KEEP-NONE-SECONDARY-NEXT: (elem (i32.const 0) $1)
+;; KEEP-NONE-SECONDARY-NEXT: (func $0 (param $0 i32) (result i32)
+;; KEEP-NONE-SECONDARY-NEXT: (call $1
+;; KEEP-NONE-SECONDARY-NEXT: (i32.const 1)
+;; KEEP-NONE-SECONDARY-NEXT: )
+;; KEEP-NONE-SECONDARY-NEXT: )
+;; KEEP-NONE-SECONDARY-NEXT: (func $1 (param $0 i32) (result i32)
+;; KEEP-NONE-SECONDARY-NEXT: (call $0
+;; KEEP-NONE-SECONDARY-NEXT: (i32.const 0)
+;; KEEP-NONE-SECONDARY-NEXT: )
+;; KEEP-NONE-SECONDARY-NEXT: )
+;; KEEP-NONE-SECONDARY-NEXT: )
+
+;; KEEP-FOO: Keeping functions: foo{{$}}
+;; KEEP-FOO-NEXT: Splitting out functions: bar{{$}}
+
+;; KEEP-FOO-PRIMARY: (module
+;; KEEP-FOO-PRIMARY-NEXT: (type $i32_=>_i32 (func (param i32) (result i32)))
+;; KEEP-FOO-PRIMARY-NEXT: (import "placeholder" "1" (func $fimport$0 (param i32) (result i32)))
+;; KEEP-FOO-PRIMARY-NEXT: (table $0 2 2 funcref)
+;; KEEP-FOO-PRIMARY-NEXT: (elem (i32.const 0) $0 $fimport$0)
+;; KEEP-FOO-PRIMARY-NEXT: (export "%foo" (func $0))
+;; KEEP-FOO-PRIMARY-NEXT: (export "%table" (table $0))
+;; KEEP-FOO-PRIMARY-NEXT: (func $0 (param $0 i32) (result i32)
+;; KEEP-FOO-PRIMARY-NEXT: (call_indirect (type $i32_=>_i32)
+;; KEEP-FOO-PRIMARY-NEXT: (i32.const 0)
+;; KEEP-FOO-PRIMARY-NEXT: (i32.const 1)
+;; KEEP-FOO-PRIMARY-NEXT: )
+;; KEEP-FOO-PRIMARY-NEXT: )
+;; KEEP-FOO-PRIMARY-NEXT: )
+
+;; KEEP-FOO-SECONDARY: (module
+;; KEEP-FOO-SECONDARY-NEXT: (type $i32_=>_i32 (func (param i32) (result i32)))
+;; KEEP-FOO-SECONDARY-NEXT: (import "primary" "%table" (table $timport$0 2 2 funcref))
+;; KEEP-FOO-SECONDARY-NEXT: (elem (i32.const 1) $0)
+;; KEEP-FOO-SECONDARY-NEXT: (import "primary" "%foo" (func $fimport$0 (param i32) (result i32)))
+;; KEEP-FOO-SECONDARY-NEXT: (func $0 (param $0 i32) (result i32)
+;; KEEP-FOO-SECONDARY-NEXT: (call $fimport$0
+;; KEEP-FOO-SECONDARY-NEXT: (i32.const 1)
+;; KEEP-FOO-SECONDARY-NEXT: )
+;; KEEP-FOO-SECONDARY-NEXT: )
+;; KEEP-FOO-SECONDARY-NEXT: )
+
+;; KEEP-BAR: Keeping functions: bar{{$}}
+;; KEEP-BAR-NEXT: Splitting out functions: foo{{$}}
+
+;; KEEP-BAR-PRIMARY: (module
+;; KEEP-BAR-PRIMARY-NEXT: (type $i32_=>_i32 (func (param i32) (result i32)))
+;; KEEP-BAR-PRIMARY-NEXT: (import "placeholder" "0" (func $fimport$0 (param i32) (result i32)))
+;; KEEP-BAR-PRIMARY-NEXT: (table $0 1 1 funcref)
+;; KEEP-BAR-PRIMARY-NEXT: (elem (i32.const 0) $fimport$0)
+;; KEEP-BAR-PRIMARY-NEXT: (export "%bar" (func $0))
+;; KEEP-BAR-PRIMARY-NEXT: (export "%table" (table $0))
+;; KEEP-BAR-PRIMARY-NEXT: (func $0 (param $0 i32) (result i32)
+;; KEEP-BAR-PRIMARY-NEXT: (call_indirect (type $i32_=>_i32)
+;; KEEP-BAR-PRIMARY-NEXT: (i32.const 1)
+;; KEEP-BAR-PRIMARY-NEXT: (i32.const 0)
+;; KEEP-BAR-PRIMARY-NEXT: )
+;; KEEP-BAR-PRIMARY-NEXT: )
+;; KEEP-BAR-PRIMARY-NEXT: )
+
+;; KEEP-BAR-SECONDARY: (module
+;; KEEP-BAR-SECONDARY-NEXT: (type $i32_=>_i32 (func (param i32) (result i32)))
+;; KEEP-BAR-SECONDARY-NEXT: (import "primary" "%table" (table $timport$0 1 1 funcref))
+;; KEEP-BAR-SECONDARY-NEXT: (elem (i32.const 0) $0)
+;; KEEP-BAR-SECONDARY-NEXT: (import "primary" "%bar" (func $fimport$0 (param i32) (result i32)))
+;; KEEP-BAR-SECONDARY-NEXT: (func $0 (param $0 i32) (result i32)
+;; KEEP-BAR-SECONDARY-NEXT: (call $fimport$0
+;; KEEP-BAR-SECONDARY-NEXT: (i32.const 0)
+;; KEEP-BAR-SECONDARY-NEXT: )
+;; KEEP-BAR-SECONDARY-NEXT: )
+;; KEEP-BAR-SECONDARY-NEXT: )
+
+;; KEEP-BOTH: warning: not splitting any functions out to the secondary module
+;; KEEP-BOTH-NEXT: Keeping functions: bar, foo{{$}}
+;; KEEP-BOTH-NEXT: Splitting out functions:{{$}}
+
+;; KEEP-BOTH-PRIMARY: (module
+;; KEEP-BOTH-PRIMARY-NEXT: (type $i32_=>_i32 (func (param i32) (result i32)))
+;; KEEP-BOTH-PRIMARY-NEXT: (table $0 1 1 funcref)
+;; KEEP-BOTH-PRIMARY-NEXT: (elem (i32.const 0) $0)
+;; KEEP-BOTH-PRIMARY-NEXT: (export "%table" (table $0))
+;; KEEP-BOTH-PRIMARY-NEXT: (func $0 (param $0 i32) (result i32)
+;; KEEP-BOTH-PRIMARY-NEXT: (call $1
+;; KEEP-BOTH-PRIMARY-NEXT: (i32.const 0)
+;; KEEP-BOTH-PRIMARY-NEXT: )
+;; KEEP-BOTH-PRIMARY-NEXT: )
+;; KEEP-BOTH-PRIMARY-NEXT: (func $1 (param $0 i32) (result i32)
+;; KEEP-BOTH-PRIMARY-NEXT: (call $0
+;; KEEP-BOTH-PRIMARY-NEXT: (i32.const 1)
+;; KEEP-BOTH-PRIMARY-NEXT: )
+;; KEEP-BOTH-PRIMARY-NEXT: )
+;; KEEP-BOTH-PRIMARY-NEXT: )
+
+;; KEEP-BOTH-SECONDARY: (module
+;; KEEP-BOTH-SECONDARY-NEXT: (import "primary" "%table" (table $timport$0 1 1 funcref))
+;; KEEP-BOTH-SECONDARY-NEXT: )
diff --git a/test/lit/wasm-split/invalid-options.wast b/test/lit/wasm-split/invalid-options.wast
new file mode 100644
index 000000000..f4ef5b6b8
--- /dev/null
+++ b/test/lit/wasm-split/invalid-options.wast
@@ -0,0 +1,64 @@
+;; Test that invalid command line option combinations are properly rejected with
+;; helpful error messages.
+
+;; --instrument cannot be used with --profile
+;; RUN: not wasm-split %s --instrument --profile %t 2>&1 \
+;; RUN: | filecheck %s --check-prefix INSTRUMENT-PROFILE
+
+;; --instrument cannot be used with -o1
+;; RUN: not wasm-split %s --instrument -o1 %t 2>&1 \
+;; RUN: | filecheck %s --check-prefix INSTRUMENT-OUT1
+
+;; --instrument cannot be used with -o2
+;; RUN: not wasm-split %s --instrument -o2 %t 2>&1 \
+;; RUN: | filecheck %s --check-prefix INSTRUMENT-OUT2
+
+;; --instrument cannot be used with --import-namespace
+;; RUN: not wasm-split %s --instrument --import-namespace=foo 2>&1 \
+;; RUN: | filecheck %s --check-prefix INSTRUMENT-IMPORT-NS
+
+;; --instrument cannot be used with --placeholder-namespace
+;; RUN: not wasm-split %s --instrument --placeholder-namespace=foo 2>&1 \
+;; RUN: | filecheck %s --check-prefix INSTRUMENT-PLACEHOLDER-NS
+
+;; --instrument cannot be used with --export-prefix
+;; RUN: not wasm-split %s --instrument --export-prefix=foo 2>&1 \
+;; RUN: | filecheck %s --check-prefix INSTRUMENT-EXPORT-PREFIX
+
+;; --instrument cannot be used with --keep-funcs
+;; RUN: not wasm-split %s --instrument --keep-funcs=foo 2>&1 \
+;; RUN: | filecheck %s --check-prefix INSTRUMENT-KEEP-FUNCS
+
+;; --instrument cannot be used with --split-funcs
+;; RUN: not wasm-split %s --instrument --split-funcs=foo 2>&1 \
+;; RUN: | filecheck %s --check-prefix INSTRUMENT-SPLIT-FUNCS
+
+;; --instrument requires -o1 and -o2 rather than -o
+;; RUN: not wasm-split %s -o %t 2>&1 \
+;; RUN: | filecheck %s --check-prefix NO-INSTRUMENT-OUT
+
+;; --instrument is required to use --profile-export
+;; RUN: not wasm-split %s --profile-export=foo 2>&1 \
+;; RUN: | filecheck %s --check-prefix NO-INSTRUMENT-PROFILE-EXPORT
+
+;; INSTRUMENT-PROFILE: error: --profile cannot be used with --instrument
+
+;; INSTRUMENT-OUT1: error: primary output cannot be used with --instrument
+
+;; INSTRUMENT-OUT2: error: secondary output cannot be used with --instrument
+
+;; INSTRUMENT-IMPORT-NS: error: --import-namespace cannot be used with --instrument
+
+;; INSTRUMENT-PLACEHOLDER-NS: error: --placeholder-namespace cannot be used with --instrument
+
+;; INSTRUMENT-EXPORT-PREFIX: error: --export-prefix cannot be used with --instrument
+
+;; INSTRUMENT-KEEP-FUNCS: error: --keep-funcs cannot be used with --instrument
+
+;; INSTRUMENT-SPLIT-FUNCS: error: --split-funcs cannot be used with --instrument
+
+;; NO-INSTRUMENT-OUT: error: must provide separate primary and secondary output with -o1 and -o2
+
+;; NO-INSTRUMENT-PROFILE-EXPORT: error: --profile-export must be used with --instrument
+
+(module)
diff --git a/test/lit/wasm-split/verbose.wast b/test/lit/wasm-split/verbose.wast
new file mode 100644
index 000000000..cdedbfae6
--- /dev/null
+++ b/test/lit/wasm-split/verbose.wast
@@ -0,0 +1,14 @@
+;; Test that --verbose mode correctly prints the kept and split funcs
+
+;; RUN: wasm-split %s --keep-funcs=foo,bar --split-funcs=baz --verbose \
+;; RUN: -o1 %t1.wasm -o2 %t2.wasm | filecheck %s
+
+;; CHECK: Keeping functions: bar, foo{{$}}
+;; CHECK: Splitting out functions: baz, quux{{$}}
+
+(module
+ (func $foo)
+ (func $bar)
+ (func $baz)
+ (func $quux)
+)