diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/tools/wasm-split/split-options.cpp | 36 | ||||
-rw-r--r-- | src/tools/wasm-split/split-options.h | 4 | ||||
-rw-r--r-- | src/tools/wasm-split/wasm-split.cpp | 84 |
3 files changed, 121 insertions, 3 deletions
diff --git a/src/tools/wasm-split/split-options.cpp b/src/tools/wasm-split/split-options.cpp index 825efddd9..e77957f1f 100644 --- a/src/tools/wasm-split/split-options.cpp +++ b/src/tools/wasm-split/split-options.cpp @@ -61,6 +61,9 @@ std::ostream& operator<<(std::ostream& o, WasmSplitOptions::Mode& mode) { case WasmSplitOptions::Mode::Split: o << "split"; break; + case WasmSplitOptions::Mode::MultiSplit: + o << "multi-split"; + break; case WasmSplitOptions::Mode::Instrument: o << "instrument"; break; @@ -91,7 +94,14 @@ WasmSplitOptions::WasmSplitOptions() "Split an input module into two output modules. The default mode.", WasmSplitOption, Options::Arguments::Zero, - [&](Options* o, const std::string& arugment) { mode = Mode::Split; }) + [&](Options* o, const std::string& argument) { mode = Mode::Split; }) + .add( + "--multi-split", + "", + "Split an input module into an arbitrary number of output modules.", + WasmSplitOption, + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { mode = Mode::MultiSplit; }) .add( "--instrument", "", @@ -151,6 +161,25 @@ WasmSplitOptions::WasmSplitOptions() [&](Options* o, const std::string& argument) { splitFuncs = parseNameList(argument); }) + .add( + "--manifest", + "", + "File describing the functions to be split into each module. Each " + "section separated by a blank line begins with the base name of an " + "output module, which is followed by a list of functions to place in " + "that module, one per line.", + WasmSplitOption, + {Mode::MultiSplit}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { manifestFile = argument; }) + .add("--out-prefix", + "", + "Prefix prepended to module names in the manifest file to create " + "output file names.", + WasmSplitOption, + {Mode::MultiSplit}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { outPrefix = argument; }) .add("--primary-output", "-o1", "Output file for the primary module.", @@ -313,7 +342,7 @@ WasmSplitOptions::WasmSplitOptions() "-g", "Emit names section in wasm binary (or full debuginfo in wast)", WasmSplitOption, - {Mode::Split, Mode::Instrument}, + {Mode::Split, Mode::MultiSplit, Mode::Instrument}, Options::Arguments::Zero, [&](Options* o, const std::string& arguments) { passOptions.debugInfo = true; @@ -322,7 +351,7 @@ WasmSplitOptions::WasmSplitOptions() "-o", "Output file.", WasmSplitOption, - {Mode::Instrument, Mode::MergeProfiles}, + {Mode::Instrument, Mode::MergeProfiles, Mode::MultiSplit}, Options::Arguments::One, [&](Options* o, const std::string& argument) { output = argument; }) .add("--unescape", @@ -407,6 +436,7 @@ bool WasmSplitOptions::validate() { } switch (mode) { case Mode::Split: + case Mode::MultiSplit: case Mode::Instrument: if (inputFiles.size() > 1) { fail("Cannot have more than one input file."); diff --git a/src/tools/wasm-split/split-options.h b/src/tools/wasm-split/split-options.h index b8129f29b..105c90c80 100644 --- a/src/tools/wasm-split/split-options.h +++ b/src/tools/wasm-split/split-options.h @@ -26,6 +26,7 @@ const std::string DEFAULT_PROFILE_EXPORT("__write_profile"); struct WasmSplitOptions : ToolOptions { enum class Mode : unsigned { Split, + MultiSplit, Instrument, MergeProfiles, PrintProfile, @@ -68,6 +69,9 @@ struct WasmSplitOptions : ToolOptions { std::string secondaryMemoryName; std::string exportPrefix; + std::string manifestFile; + std::string outPrefix; + // 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. diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp index cb148090d..ea1734b6b 100644 --- a/src/tools/wasm-split/wasm-split.cpp +++ b/src/tools/wasm-split/wasm-split.cpp @@ -362,6 +362,87 @@ void splitModule(const WasmSplitOptions& options) { writeModule(*secondary, options.secondaryOutput, options); } +void multiSplitModule(const WasmSplitOptions& options) { + if (options.manifestFile.empty()) { + Fatal() << "--multi-split requires --manifest"; + } + if (options.output.empty()) { + Fatal() << "--multi-split requires --output"; + } + + std::ifstream manifest(options.manifestFile); + if (!manifest.is_open()) { + Fatal() << "File not found: " << options.manifestFile; + } + + Module wasm; + parseInput(wasm, options); + + // Map module names to the functions that should be in the modules. + std::map<std::string, std::unordered_set<std::string>> moduleFuncs; + // The module for which we are currently parsing a set of functions. + std::string currModule; + // The set of functions we are currently inserting into. + std::unordered_set<std::string>* currFuncs = nullptr; + // Map functions to their modules to ensure no function is assigned to + // multiple modules. + std::unordered_map<std::string, std::string> funcModules; + + std::string line; + bool newSection = true; + while (std::getline(manifest, line)) { + if (line.empty()) { + newSection = true; + continue; + } + if (newSection) { + currModule = line; + currFuncs = &moduleFuncs[line]; + newSection = false; + continue; + } + assert(currFuncs); + currFuncs->insert(line); + auto [it, inserted] = funcModules.insert({line, currModule}); + if (!inserted && it->second != currModule) { + Fatal() << "Function " << line << "cannot be assigned to module " + << currModule << "; it is already assigned to module " + << it->second << '\n'; + } + if (inserted && !options.quiet && !wasm.getFunctionOrNull(line)) { + std::cerr << "warning: Function " << line << " does not exist\n"; + } + } + + ModuleSplitting::Config config; + config.usePlaceholders = false; + config.importNamespace = ""; + config.minimizeNewExportNames = true; + for (auto& func : wasm.functions) { + config.primaryFuncs.insert(func->name); + } + for (auto& [mod, funcs] : moduleFuncs) { + if (options.verbose) { + std::cerr << "Splitting module " << mod << '\n'; + } + if (!options.quiet && funcs.empty()) { + std::cerr << "warning: Module " << mod << " will be empty\n"; + } + for (auto& func : funcs) { + config.primaryFuncs.erase(Name(func)); + } + auto splitResults = ModuleSplitting::splitFunctions(wasm, config); + // TODO: symbolMap, placeholderMap, emitModuleNames + // TODO: Support --emit-text and use .wast in that case. + auto moduleName = options.outPrefix + mod + ".wasm"; + PassRunner runner(&*splitResults.secondary); + runner.add("remove-unused-module-elements"); + runner.run(); + writeModule(*splitResults.secondary, moduleName, options); + } + writeModule(wasm, options.output, options); +} + void mergeProfiles(const WasmSplitOptions& options) { // Read the initial profile. We will merge other profiles into this one. ProfileData data = readProfile(options.inputFiles[0]); @@ -503,6 +584,9 @@ int main(int argc, const char* argv[]) { case WasmSplitOptions::Mode::Split: splitModule(options); break; + case WasmSplitOptions::Mode::MultiSplit: + multiSplitModule(options); + break; case WasmSplitOptions::Mode::Instrument: instrumentModule(options); break; |