Skip to content

Commit dd4dc47

Browse files
authored
[wasm-split] Do multi-split at once (#7956)
This does multi-splitting of modules at once, rather than splitting them one by one by doing 2-way split n times. Previously when we did multi-splitting we split the 1st module as the "secondary" module assuming all other functions belonging to 2nd-nth modules as "primary" module. And then we repeat the same task for the 2nd module, assuming 3rd-nth module functions belong to the "primary" module. This unnecessarily repeated some tasks that could have been done once. This reduces the running time on a reproducer provided by @biggs0125 before (to fix #7725) from 232s to 80s on my machine, reducing it by around 66%. Some side-products of this PR are: - Now we only create a single table to host placeholders (or `ref.null`s in case of `--no-placeholders`) even when reference-types is enabled. Previously we created a table per secondary module, resulting in n tables. - The names of trampoline functions have been changed in the tests, but semantically they are the same. (e.g. in `test/lit/wasm-split/multi-split.wast`) The reason for the change is, previously we split modules one by one, by the time we split the first module, it assumed functions belonging to other secondary modules were primary functions, but they later changed to trampolines as well. Now they are all named as trampolines, arguably enhancing readability.
1 parent c326e66 commit dd4dc47

File tree

8 files changed

+401
-375
lines changed

8 files changed

+401
-375
lines changed

src/ir/module-splitting.cpp

Lines changed: 248 additions & 192 deletions
Large diffs are not rendered by default.

src/ir/module-splitting.h

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,12 @@ namespace wasm::ModuleSplitting {
4747
static const Name LOAD_SECONDARY_MODULE("__load_secondary_module");
4848

4949
struct Config {
50-
// The set of functions to split into the secondary module. All others are
51-
// kept in the primary module. Must not include the start function if it
52-
// exists. May or may not include imported functions, which are always kept in
53-
// the primary module regardless.
54-
std::set<Name> secondaryFuncs;
50+
// A vector of set of functions to split into that secondary. Each function
51+
// set belongs to a single secondary module. All others are kept in the
52+
// primary module. Must not include the start function if it exists. May or
53+
// may not include imported functions, which are always kept in the primary
54+
// module regardless.
55+
std::vector<std::set<Name>> secondaryFuncs;
5556
// Whether to import placeholder functions into the primary module that will
5657
// be called when a secondary function is called before the secondary module
5758
// has been loaded.
@@ -76,7 +77,7 @@ struct Config {
7677
};
7778

7879
struct Results {
79-
std::unique_ptr<Module> secondary;
80+
std::vector<std::unique_ptr<Module>> secondaries;
8081
std::unordered_map<Name, std::map<size_t, Name>> placeholderMap;
8182
};
8283

src/tools/wasm-split/wasm-split.cpp

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ void splitModule(const WasmSplitOptions& options) {
329329

330330
// Actually perform the splitting
331331
ModuleSplitting::Config config;
332-
config.secondaryFuncs = std::move(splitFuncs);
332+
config.secondaryFuncs.push_back(std::move(splitFuncs));
333333
if (options.importNamespace.size()) {
334334
config.importNamespace = options.importNamespace;
335335
}
@@ -343,7 +343,7 @@ void splitModule(const WasmSplitOptions& options) {
343343
config.minimizeNewExportNames = !options.passOptions.debugInfo;
344344
config.jspi = options.jspi;
345345
auto splitResults = ModuleSplitting::splitFunctions(wasm, config);
346-
auto& secondary = splitResults.secondary;
346+
auto& secondary = *splitResults.secondaries.begin();
347347

348348
adjustTableSize(wasm, options.initialTableSize);
349349
adjustTableSize(*secondary, options.initialTableSize, /*secondary=*/true);
@@ -389,27 +389,37 @@ void multiSplitModule(const WasmSplitOptions& options) {
389389
Module wasm;
390390
parseInput(wasm, options);
391391

392-
// Map module names to the functions that should be in the modules.
393-
std::map<Name, std::unordered_set<Name>> moduleFuncs;
394392
// The module for which we are currently parsing a set of functions.
395393
Name currModule;
396394
// The set of functions we are currently inserting into.
397-
std::unordered_set<Name>* currFuncs = nullptr;
395+
std::set<Name>* currFuncs = nullptr;
398396
// Map functions to their modules to ensure no function is assigned to
399397
// multiple modules.
400398
std::unordered_map<Name, Name> funcModules;
401399

400+
ModuleSplitting::Config config;
402401
std::string line;
403402
bool newSection = true;
403+
std::vector<Name> moduleNames;
404+
std::unordered_set<Name> moduleNameSet;
404405
while (std::getline(manifest, line)) {
405406
if (line.empty()) {
406407
newSection = true;
408+
if (currFuncs->empty() && !options.quiet) {
409+
std::cerr << "warning: Module " << currModule << " will be empty\n";
410+
}
407411
continue;
408412
}
409413
Name name = WasmBinaryReader::escape(line);
410414
if (newSection) {
415+
if (moduleNameSet.count(name)) {
416+
Fatal() << "Module name " << name << " is listed more than once\n";
417+
}
411418
currModule = name;
412-
currFuncs = &moduleFuncs[name];
419+
moduleNameSet.insert(currModule);
420+
moduleNames.push_back(currModule);
421+
config.secondaryFuncs.emplace_back(std::set<Name>());
422+
currFuncs = &config.secondaryFuncs.back();
413423
newSection = false;
414424
continue;
415425
}
@@ -426,42 +436,33 @@ void multiSplitModule(const WasmSplitOptions& options) {
426436
}
427437
}
428438

429-
ModuleSplitting::Config config;
430439
config.usePlaceholders = options.usePlaceholders;
431440
config.importNamespace = options.importNamespace;
432441
config.minimizeNewExportNames = !options.passOptions.debugInfo;
433442
if (options.emitModuleNames && !wasm.name) {
434443
wasm.name = Path::getBaseName(options.output);
435444
}
436445

437-
std::unordered_map<Name, std::map<size_t, Name>> placeholderMap;
438-
for (auto& [mod, funcs] : moduleFuncs) {
439-
if (options.verbose) {
440-
std::cerr << "Splitting module " << mod << '\n';
441-
}
442-
if (!options.quiet && funcs.empty()) {
443-
std::cerr << "warning: Module " << mod << " will be empty\n";
444-
}
445-
config.secondaryFuncs = std::set<Name>(funcs.begin(), funcs.end());
446-
auto splitResults = ModuleSplitting::splitFunctions(wasm, config);
447-
auto moduleName = options.outPrefix + mod.toString() +
446+
auto splitResults = ModuleSplitting::splitFunctions(wasm, config);
447+
assert(moduleNames.size() == splitResults.secondaries.size());
448+
for (Index i = 0, n = moduleNames.size(); i < n; i++) {
449+
auto& secondary = *splitResults.secondaries[i];
450+
auto moduleName = options.outPrefix + moduleNames[i].toString() +
448451
(options.emitBinary ? ".wasm" : ".wast");
449452
if (options.symbolMap) {
450-
writeSymbolMap(*splitResults.secondary, moduleName + ".symbols");
451-
}
452-
if (options.placeholderMap) {
453-
placeholderMap.merge(splitResults.placeholderMap);
453+
writeSymbolMap(secondary, moduleName + ".symbols");
454454
}
455455
if (options.emitModuleNames) {
456-
splitResults.secondary->name = Path::getBaseName(moduleName);
456+
secondary.name = Path::getBaseName(moduleName);
457457
}
458-
writeModule(*splitResults.secondary, moduleName, options);
458+
writeModule(secondary, moduleName, options);
459459
}
460460
if (options.symbolMap) {
461461
writeSymbolMap(wasm, options.output + ".symbols");
462462
}
463463
if (options.placeholderMap) {
464-
writePlaceholderMap(wasm, placeholderMap, options.output + ".placeholders");
464+
writePlaceholderMap(
465+
wasm, splitResults.placeholderMap, options.output + ".placeholders");
465466
}
466467
writeModule(wasm, options.output, options);
467468
}

test/example/module-splitting.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,10 @@ void do_test(const std::set<Name>& keptFuncs, std::string&& module) {
5151
std::cout << "\n";
5252

5353
ModuleSplitting::Config config;
54-
config.secondaryFuncs = std::move(splitFuncs);
54+
config.secondaryFuncs.push_back(std::move(splitFuncs));
5555
config.newExportPrefix = "%";
56-
auto secondary = splitFunctions(*primary, config).secondary;
56+
auto results = splitFunctions(*primary, config);
57+
auto& secondary = *results.secondaries.begin();
5758

5859
std::cout << "After:\n";
5960
std::cout << *primary.get();
@@ -475,11 +476,12 @@ void test_minimized_exports() {
475476
primary.addFunction(Builder::makeFunction("call", funcType, {}, callBody));
476477

477478
ModuleSplitting::Config config;
478-
config.secondaryFuncs = {"call"};
479+
config.secondaryFuncs.push_back({"call"});
479480
config.newExportPrefix = "%";
480481
config.minimizeNewExportNames = true;
481482

482-
auto secondary = splitFunctions(primary, config).secondary;
483+
auto results = splitFunctions(primary, config);
484+
auto& secondary = *results.secondaries.begin();
483485
std::cout << "Minimized names primary:\n";
484486
std::cout << primary << "\n";
485487
std::cout << "Minimized names secondary:\n";

test/lit/wasm-split/multi-split-escape-names.wast

Lines changed: 32 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
;; RUN: wasm-dis %t3.wasm | filecheck %s --check-prefix=MOD3
88

99
(module
10+
;; PRIMARY: (type $ret-i64 (func (result i64)))
11+
12+
;; PRIMARY: (type $ret-f32 (func (result f32)))
13+
1014
;; PRIMARY: (type $ret-i32 (func (result i32)))
1115
(type $ret-i32 (func (result i32)))
12-
;; PRIMARY: (type $ret-i64 (func (result i64)))
1316
(type $ret-i64 (func (result i64)))
14-
;; PRIMARY: (type $ret-f32 (func (result f32)))
1517
(type $ret-f32 (func (result f32)))
1618

1719
;; MOD1: (type $0 (func (result f32)))
@@ -20,13 +22,13 @@
2022

2123
;; MOD1: (type $2 (func (result i32)))
2224

23-
;; MOD1: (import "" "table" (table $timport$0 1 funcref))
25+
;; MOD1: (import "" "table" (table $timport$0 3 funcref))
2426

25-
;; MOD1: (import "" "std::operator<<\\28std::__2::basic_ostream<char\\2c\\20std::__2::char_traits<char>>&\\2c\\20wasm::Module&\\29" (func $std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29 (result f32)))
27+
;; MOD1: (import "" "trampoline_std::operator<<\\28std::__2::basic_ostream<char\\2c\\20std::__2::char_traits<char>>&\\2c\\20wasm::Module&\\29" (func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29 (result f32)))
2628

27-
;; MOD1: (import "" "wasm::Literal::Literal\\28std::__2::array<wasm::Literal\\2c\\204ul>\\20const&\\29" (func $wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29 (result i64)))
29+
;; MOD1: (import "" "trampoline_wasm::Literal::Literal\\28std::__2::array<wasm::Literal\\2c\\204ul>\\20const&\\29" (func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29 (result i64)))
2830

29-
;; MOD1: (elem $0 (i32.const 0) $wasm::Type::getFeatures\28\29\20const)
31+
;; MOD1: (elem $0 (i32.const 2) $wasm::Type::getFeatures\28\29\20const)
3032

3133
;; MOD1: (func $wasm::Type::getFeatures\28\29\20const (result i32)
3234
;; MOD1-NEXT: (drop
@@ -36,12 +38,12 @@
3638
;; MOD1-NEXT: )
3739
;; MOD1-NEXT: (drop
3840
;; MOD1-NEXT: (call_ref $1
39-
;; MOD1-NEXT: (ref.func $wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29)
41+
;; MOD1-NEXT: (ref.func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29)
4042
;; MOD1-NEXT: )
4143
;; MOD1-NEXT: )
4244
;; MOD1-NEXT: (drop
4345
;; MOD1-NEXT: (call_ref $0
44-
;; MOD1-NEXT: (ref.func $std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29)
46+
;; MOD1-NEXT: (ref.func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29)
4547
;; MOD1-NEXT: )
4648
;; MOD1-NEXT: )
4749
;; MOD1-NEXT: (i32.const 0)
@@ -71,9 +73,9 @@
7173

7274
;; MOD2: (type $2 (func (result i64)))
7375

74-
;; MOD2: (import "" "table_4" (table $timport$0 1 funcref))
76+
;; MOD2: (import "" "table" (table $timport$0 3 funcref))
7577

76-
;; MOD2: (import "" "std::operator<<\\28std::__2::basic_ostream<char\\2c\\20std::__2::char_traits<char>>&\\2c\\20wasm::Module&\\29" (func $std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29 (result f32)))
78+
;; MOD2: (import "" "trampoline_std::operator<<\\28std::__2::basic_ostream<char\\2c\\20std::__2::char_traits<char>>&\\2c\\20wasm::Module&\\29" (func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29 (result f32)))
7779

7880
;; MOD2: (import "" "trampoline_wasm::Type::getFeatures\\28\\29\\20const" (func $trampoline_wasm::Type::getFeatures\28\29\20const (result i32)))
7981

@@ -92,7 +94,7 @@
9294
;; MOD2-NEXT: )
9395
;; MOD2-NEXT: (drop
9496
;; MOD2-NEXT: (call_ref $0
95-
;; MOD2-NEXT: (ref.func $std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29)
97+
;; MOD2-NEXT: (ref.func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29)
9698
;; MOD2-NEXT: )
9799
;; MOD2-NEXT: )
98100
;; MOD2-NEXT: (i64.const 0)
@@ -122,13 +124,13 @@
122124

123125
;; MOD3: (type $2 (func (result f32)))
124126

125-
;; MOD3: (import "" "table_5" (table $timport$0 1 funcref))
127+
;; MOD3: (import "" "table" (table $timport$0 3 funcref))
126128

127-
;; MOD3: (import "" "wasm::Literal::Literal\\28std::__2::array<wasm::Literal\\2c\\204ul>\\20const&\\29" (func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29 (result i64)))
129+
;; MOD3: (import "" "trampoline_wasm::Literal::Literal\\28std::__2::array<wasm::Literal\\2c\\204ul>\\20const&\\29" (func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29 (result i64)))
128130

129131
;; MOD3: (import "" "trampoline_wasm::Type::getFeatures\\28\\29\\20const" (func $trampoline_wasm::Type::getFeatures\28\29\20const (result i32)))
130132

131-
;; MOD3: (elem $0 (i32.const 0) $std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29)
133+
;; MOD3: (elem $0 (i32.const 1) $std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29)
132134

133135
;; MOD3: (func $std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29 (result f32)
134136
;; MOD3-NEXT: (drop
@@ -167,50 +169,38 @@
167169
(f32.const 0)
168170
)
169171
)
170-
;; PRIMARY: (import "placeholder" "0" (func $placeholder_0 (result i32)))
172+
;; PRIMARY: (import "placeholder" "0" (func $placeholder_0 (result i64)))
171173

172-
;; PRIMARY: (import "placeholder" "0" (func $placeholder_0_4 (result i64)))
174+
;; PRIMARY: (import "placeholder" "1" (func $placeholder_1 (result f32)))
173175

174-
;; PRIMARY: (import "placeholder" "0" (func $placeholder_0_5 (result f32)))
176+
;; PRIMARY: (import "placeholder" "2" (func $placeholder_2 (result i32)))
175177

176-
;; PRIMARY: (table $0 1 funcref)
178+
;; PRIMARY: (table $0 3 funcref)
177179

178-
;; PRIMARY: (table $1 1 funcref)
180+
;; PRIMARY: (elem $0 (i32.const 0) $placeholder_0 $placeholder_1 $placeholder_2)
179181

180-
;; PRIMARY: (table $2 1 funcref)
182+
;; PRIMARY: (export "trampoline_std::operator<<\\28std::__2::basic_ostream<char\\2c\\20std::__2::char_traits<char>>&\\2c\\20wasm::Module&\\29" (func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29))
181183

182-
;; PRIMARY: (elem $0 (table $0) (i32.const 0) func $placeholder_0)
183-
184-
;; PRIMARY: (elem $1 (table $1) (i32.const 0) func $placeholder_0_4)
185-
186-
;; PRIMARY: (elem $2 (table $2) (i32.const 0) func $placeholder_0_5)
187-
188-
;; PRIMARY: (export "std::operator<<\\28std::__2::basic_ostream<char\\2c\\20std::__2::char_traits<char>>&\\2c\\20wasm::Module&\\29" (func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29))
189-
190-
;; PRIMARY: (export "wasm::Literal::Literal\\28std::__2::array<wasm::Literal\\2c\\204ul>\\20const&\\29" (func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29))
191-
192-
;; PRIMARY: (export "table" (table $0))
184+
;; PRIMARY: (export "trampoline_wasm::Literal::Literal\\28std::__2::array<wasm::Literal\\2c\\204ul>\\20const&\\29" (func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29))
193185

194186
;; PRIMARY: (export "trampoline_wasm::Type::getFeatures\\28\\29\\20const" (func $trampoline_wasm::Type::getFeatures\28\29\20const))
195187

196-
;; PRIMARY: (export "table_4" (table $1))
197-
198-
;; PRIMARY: (export "table_5" (table $2))
188+
;; PRIMARY: (export "table" (table $0))
199189

200-
;; PRIMARY: (func $trampoline_wasm::Type::getFeatures\28\29\20const (result i32)
201-
;; PRIMARY-NEXT: (call_indirect (type $ret-i32)
190+
;; PRIMARY: (func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29 (result i64)
191+
;; PRIMARY-NEXT: (call_indirect (type $ret-i64)
202192
;; PRIMARY-NEXT: (i32.const 0)
203193
;; PRIMARY-NEXT: )
204194
;; PRIMARY-NEXT: )
205195

206-
;; PRIMARY: (func $trampoline_wasm::Literal::Literal\28std::__2::array<wasm::Literal\2c\204ul>\20const&\29 (result i64)
207-
;; PRIMARY-NEXT: (call_indirect $1 (type $ret-i64)
208-
;; PRIMARY-NEXT: (i32.const 0)
196+
;; PRIMARY: (func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29 (result f32)
197+
;; PRIMARY-NEXT: (call_indirect (type $ret-f32)
198+
;; PRIMARY-NEXT: (i32.const 1)
209199
;; PRIMARY-NEXT: )
210200
;; PRIMARY-NEXT: )
211201

212-
;; PRIMARY: (func $trampoline_std::operator<<\28std::__2::basic_ostream<char\2c\20std::__2::char_traits<char>>&\2c\20wasm::Module&\29 (result f32)
213-
;; PRIMARY-NEXT: (call_indirect $2 (type $ret-f32)
214-
;; PRIMARY-NEXT: (i32.const 0)
202+
;; PRIMARY: (func $trampoline_wasm::Type::getFeatures\28\29\20const (result i32)
203+
;; PRIMARY-NEXT: (call_indirect (type $ret-i32)
204+
;; PRIMARY-NEXT: (i32.const 2)
215205
;; PRIMARY-NEXT: )
216206
;; PRIMARY-NEXT: )

0 commit comments

Comments
 (0)