Skip to content

Commit e83a9e2

Browse files
authored
When legalizing, optionally export the original function too with orig$X (#2427)
The original is necessary if we want to pass it to wasm, where it will be called directly, without JS legalization. For example the JS dynamic loader in emscripten needs this, emscripten-core/emscripten#9562
1 parent 63ddbc0 commit e83a9e2

File tree

3 files changed

+62
-1
lines changed

3 files changed

+62
-1
lines changed

src/passes/LegalizeJSInterface.cpp

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,42 @@ struct LegalizeJSInterface : public Pass {
5050
LegalizeJSInterface(bool full) : full(full) {}
5151

5252
void run(PassRunner* runner, Module* module) override {
53+
auto exportOriginals =
54+
!runner->options
55+
.getArgumentOrDefault("legalize-js-interface-export-originals", "")
56+
.empty();
5357
// for each illegal export, we must export a legalized stub instead
58+
std::vector<Export*> newExports;
5459
for (auto& ex : module->exports) {
5560
if (ex->kind == ExternalKind::Function) {
5661
// if it's an import, ignore it
5762
auto* func = module->getFunction(ex->value);
5863
if (isIllegal(func) && shouldBeLegalized(ex.get(), func)) {
64+
// Provide a legal function for the export.
5965
auto legalName = makeLegalStub(func, module);
6066
ex->value = legalName;
67+
if (exportOriginals) {
68+
// Also export the original function, before legalization. This is
69+
// not normally useful for JS, except in cases like dynamic linking
70+
// where the JS loader code must copy exported wasm functions into
71+
// the table, and they must not be legalized as other wasm code will
72+
// do an indirect call to them. However, don't do this for imported
73+
// functions, as those would be legalized in their actual module
74+
// anyhow. It also makes no sense to do this for dynCalls, as they
75+
// are only called from JS.
76+
if (!func->imported() && !isDynCall(ex->name)) {
77+
Builder builder(*module);
78+
Name newName = std::string("orig$") + ex->name.str;
79+
newExports.push_back(builder.makeExport(
80+
newName, func->name, ExternalKind::Function));
81+
}
82+
}
6183
}
6284
}
6385
}
86+
for (auto* ex : newExports) {
87+
module->addExport(ex);
88+
}
6489
// Avoid iterator invalidation later.
6590
std::vector<Function*> originalFunctions;
6691
for (auto& func : module->functions) {
@@ -136,13 +161,15 @@ struct LegalizeJSInterface : public Pass {
136161
return t->result == i64;
137162
}
138163

164+
bool isDynCall(Name name) { return name.startsWith("dynCall_"); }
165+
139166
// Check if an export should be legalized.
140167
bool shouldBeLegalized(Export* ex, Function* func) {
141168
if (full) {
142169
return true;
143170
}
144171
// We are doing minimal legalization - just what JS needs.
145-
return ex->name.startsWith("dynCall_");
172+
return isDynCall(ex->name);
146173
}
147174

148175
// Check if an import should be legalized.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
(module
2+
(type $FUNCSIG$j (func (result i64)))
3+
(type $FUNCSIG$vi (func (param i32)))
4+
(import "env" "setTempRet0" (func $setTempRet0 (param i32)))
5+
(export "func" (func $legalstub$func))
6+
(export "orig$func" (func $func))
7+
(func $func (; 1 ;) (type $FUNCSIG$j) (result i64)
8+
(unreachable)
9+
)
10+
(func $legalstub$func (; 2 ;) (result i32)
11+
(local $0 i64)
12+
(local.set $0
13+
(call $func)
14+
)
15+
(call $setTempRet0
16+
(i32.wrap_i64
17+
(i64.shr_u
18+
(local.get $0)
19+
(i64.const 32)
20+
)
21+
)
22+
)
23+
(i32.wrap_i64
24+
(local.get $0)
25+
)
26+
)
27+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(module
2+
(export "func" (func $func))
3+
(func $func (result i64)
4+
(unreachable)
5+
)
6+
)
7+

0 commit comments

Comments
 (0)