Skip to content

Commit 106f84b

Browse files
authored
[wasm-split] Add an option to skip importing placeholders (#6942)
Wasm-split generally assumes that calls to secondary functions made before the secondary module has been loaded and instantiated should go to imported placeholder functions that can be responsible for loading the secondary module and forwarding the call to the loaded function. That scheme makes the loading entirely transparent from the application's point of view, which is not always a good thing. Other schemes would make it impossible for a secondary function to be called before the secondary module has been explicitly loaded, in which case the placeholder functions would never be called. To improve code size and simplify instantiation under these schemes, add a new `--no-placeholders` option that skips adding imported placeholder functions.
1 parent 2a40965 commit 106f84b

7 files changed

Lines changed: 109 additions & 22 deletions

File tree

src/ir/module-splitting.cpp

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,9 @@ template<class F> void forEachElement(Module& module, F f) {
9494
} else if (auto* g = segment->offset->dynCast<GlobalGet>()) {
9595
base = g->name;
9696
}
97-
ElementUtils::iterElementSegmentFunctionNames(
98-
segment, [&](Name& entry, Index i) {
99-
f(segment->table, base, offset + i, entry);
100-
});
97+
for (Index i = 0; i < segment->data.size(); ++i) {
98+
f(segment->table, base, offset + i, segment->data[i]);
99+
}
101100
});
102101
}
103102

@@ -209,9 +208,12 @@ TableSlotManager::TableSlotManager(Module& module) : module(module) {
209208
}
210209

211210
// Initialize funcIndices with the functions already in the table.
212-
forEachElement(module, [&](Name table, Name base, Index offset, Name func) {
213-
addSlot(func, {table, base, offset});
214-
});
211+
forEachElement(module,
212+
[&](Name table, Name base, Index offset, Expression* elem) {
213+
if (auto* func = elem->dynCast<RefFunc>()) {
214+
addSlot(func->func, {table, base, offset});
215+
}
216+
});
215217
}
216218

217219
Table* TableSlotManager::makeTable() {
@@ -693,21 +695,32 @@ void ModuleSplitter::setupTablePatching() {
693695
// Replace table references to secondary functions with an imported
694696
// placeholder that encodes the table index in its name:
695697
// `importNamespace`.`index`.
696-
forEachElement(primary, [&](Name, Name, Index index, Name& elem) {
697-
if (secondaryFuncs.count(elem)) {
698-
placeholderMap[index] = elem;
699-
auto* secondaryFunc = secondary.getFunction(elem);
700-
replacedElems[index] = secondaryFunc;
701-
auto placeholder = std::make_unique<Function>();
702-
placeholder->module = config.placeholderNamespace;
703-
placeholder->base = std::to_string(index);
704-
placeholder->name = Names::getValidFunctionName(
705-
primary, std::string("placeholder_") + placeholder->base.toString());
706-
placeholder->hasExplicitName = true;
707-
placeholder->type = secondaryFunc->type;
708-
elem = placeholder->name;
709-
primary.addFunction(std::move(placeholder));
698+
forEachElement(primary, [&](Name, Name, Index index, Expression*& elem) {
699+
auto* ref = elem->dynCast<RefFunc>();
700+
if (!ref) {
701+
return;
702+
}
703+
if (!secondaryFuncs.count(ref->func)) {
704+
return;
705+
}
706+
placeholderMap[index] = ref->func;
707+
auto* secondaryFunc = secondary.getFunction(ref->func);
708+
replacedElems[index] = secondaryFunc;
709+
if (!config.usePlaceholders) {
710+
// TODO: This can create active element segments with lots of nulls. We
711+
// should optimize them like we do data segments with zeros.
712+
elem = Builder(primary).makeRefNull(HeapType::nofunc);
713+
return;
710714
}
715+
auto placeholder = std::make_unique<Function>();
716+
placeholder->module = config.placeholderNamespace;
717+
placeholder->base = std::to_string(index);
718+
placeholder->name = Names::getValidFunctionName(
719+
primary, std::string("placeholder_") + placeholder->base.toString());
720+
placeholder->hasExplicitName = true;
721+
placeholder->type = secondaryFunc->type;
722+
elem = Builder(primary).makeRefFunc(placeholder->name, placeholder->type);
723+
primary.addFunction(std::move(placeholder));
711724
});
712725

713726
if (replacedElems.size() == 0) {

src/ir/module-splitting.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,15 @@ struct Config {
5252
// exists. May or may not include imported functions, which are always kept in
5353
// the primary module regardless.
5454
std::set<Name> primaryFuncs;
55+
// Whether to import placeholder functions into the primary module that will
56+
// be called when a secondary function is called before the secondary module
57+
// has been loaded.
58+
bool usePlaceholders = true;
5559
// The namespace from which to import primary functions into the secondary
5660
// module.
5761
Name importNamespace = "primary";
5862
// The namespace from which to import placeholder functions into the primary
59-
// module.
63+
// module. Ignored if `usePlaceholders` is false.
6064
Name placeholderNamespace = "placeholder";
6165
// The prefix to attach to the name of any newly created exports. This can be
6266
// used to differentiate between "real" exports of the module and exports that

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,15 @@ WasmSplitOptions::WasmSplitOptions()
176176
{Mode::Split},
177177
Options::Arguments::Zero,
178178
[&](Options* o, const std::string& argument) { symbolMap = true; })
179+
.add(
180+
"--no-placeholders",
181+
"",
182+
"Do not import placeholder functions. Calls to secondary functions will "
183+
"fail before the secondary module has been instantiated.",
184+
WasmSplitOption,
185+
{Mode::Split},
186+
Options::Arguments::Zero,
187+
[&](Options* o, const std::string& argument) { usePlaceholders = false; })
179188
.add(
180189
"--placeholdermap",
181190
"",

src/tools/wasm-split/split-options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ struct WasmSplitOptions : ToolOptions {
4141
};
4242
StorageKind storageKind = StorageKind::InGlobals;
4343

44+
bool usePlaceholders = true;
4445
bool unescape = false;
4546
bool verbose = false;
4647
bool emitBinary = true;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ void splitModule(const WasmSplitOptions& options) {
329329
if (options.exportPrefix.size()) {
330330
config.newExportPrefix = options.exportPrefix;
331331
}
332+
config.usePlaceholders = options.usePlaceholders;
332333
config.minimizeNewExportNames = !options.passOptions.debugInfo;
333334
config.jspi = options.jspi;
334335
auto splitResults = ModuleSplitting::splitFunctions(wasm, config);

test/lit/help/wasm-split.test

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@
5252
;; CHECK-NEXT: --symbolmap [split] Write a symbol map file for each
5353
;; CHECK-NEXT: of the output modules.
5454
;; CHECK-NEXT:
55+
;; CHECK-NEXT: --no-placeholders [split] Do not import placeholder
56+
;; CHECK-NEXT: functions. Calls to secondary functions
57+
;; CHECK-NEXT: will fail before the secondary module has
58+
;; CHECK-NEXT: been instantiated.
59+
;; CHECK-NEXT:
5560
;; CHECK-NEXT: --placeholdermap [split] Write a file mapping placeholder
5661
;; CHECK-NEXT: indices to the function names.
5762
;; CHECK-NEXT:
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
2+
3+
;; RUN: wasm-split %s -all --no-placeholders --split-funcs=bar,baz -g -o1 %t.1.wasm -o2 %t.2.wasm
4+
;; RUN: wasm-dis %t.1.wasm | filecheck %s --check-prefix PRIMARY
5+
;; RUN: wasm-dis %t.2.wasm | filecheck %s --check-prefix SECONDARY
6+
7+
(module
8+
;; PRIMARY: (type $0 (func))
9+
10+
;; PRIMARY: (table $0 2 funcref)
11+
12+
;; PRIMARY: (elem $0 (table $0) (i32.const 0) funcref (item (ref.null nofunc)) (item (ref.null nofunc)))
13+
14+
;; PRIMARY: (export "foo" (func $foo))
15+
16+
;; PRIMARY: (export "table" (table $0))
17+
18+
;; PRIMARY: (func $foo
19+
;; PRIMARY-NEXT: (call_indirect (type $0)
20+
;; PRIMARY-NEXT: (i32.const 0)
21+
;; PRIMARY-NEXT: )
22+
;; PRIMARY-NEXT: (call_indirect (type $0)
23+
;; PRIMARY-NEXT: (i32.const 1)
24+
;; PRIMARY-NEXT: )
25+
;; PRIMARY-NEXT: )
26+
(func $foo
27+
(call $bar)
28+
(call $baz)
29+
)
30+
;; SECONDARY: (type $0 (func))
31+
32+
;; SECONDARY: (import "primary" "table" (table $timport$0 2 funcref))
33+
34+
;; SECONDARY: (import "primary" "foo" (func $foo))
35+
36+
;; SECONDARY: (elem $0 (i32.const 0) $bar $baz)
37+
38+
;; SECONDARY: (func $bar
39+
;; SECONDARY-NEXT: (call $foo)
40+
;; SECONDARY-NEXT: (call $baz)
41+
;; SECONDARY-NEXT: )
42+
(func $bar
43+
(call $foo)
44+
(call $baz)
45+
)
46+
;; SECONDARY: (func $baz
47+
;; SECONDARY-NEXT: (call $foo)
48+
;; SECONDARY-NEXT: (call $bar)
49+
;; SECONDARY-NEXT: )
50+
(func $baz
51+
(call $foo)
52+
(call $bar)
53+
)
54+
)

0 commit comments

Comments
 (0)