Skip to content

Commit 75c1ef8

Browse files
committed
ename Emscripten EHSjLj functions in wasm backend
Now that we rename invoke wrappers and `emscripten_longjmp_jmpbuf` in the wasm backend, this deletes all related renaming routines and relevant tests. But we still need to generate dynCalls for invokes; for that this adds dynCall generations for invokes in `GenerateDynCalls` pass, and moves related functions from wasm-emscripten.cpp to GenerateDynCalls.cpp, given that now they are only used there. Some lld tests now have new dynCalls in their results, which is expected, because they have Addresses: WebAssembly#3043 and WebAssembly#3081 Companions:
1 parent 7549fa4 commit 75c1ef8

14 files changed

+163
-300
lines changed

src/passes/GenerateDynCalls.cpp

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
#include "ir/import-utils.h"
2828
#include "pass.h"
2929
#include "support/debug.h"
30-
#include "wasm-emscripten.h"
30+
#include "wasm-builder.h"
3131

3232
#define DEBUG_TYPE "generate-dyncalls"
3333

@@ -36,21 +36,101 @@ namespace wasm {
3636
struct GenerateDynCalls : public WalkerPass<PostWalker<GenerateDynCalls>> {
3737
GenerateDynCalls(bool onlyI64) : onlyI64(onlyI64) {}
3838

39+
void doWalkModule(Module* wasm) {
40+
PostWalker<GenerateDynCalls>::doWalkModule(wasm);
41+
for (auto& sig : invokeSigs) {
42+
generateDynCallThunk(sig);
43+
}
44+
}
45+
3946
void visitTable(Table* table) {
47+
// Generate dynCalls for functions in the table
4048
if (table->segments.size() > 0) {
41-
EmscriptenGlueGenerator generator(*getModule());
42-
generator.onlyI64DynCalls = onlyI64;
4349
std::vector<Name> tableSegmentData;
4450
for (const auto& indirectFunc : table->segments[0].data) {
45-
generator.generateDynCallThunk(
46-
getModule()->getFunction(indirectFunc)->sig);
51+
generateDynCallThunk(getModule()->getFunction(indirectFunc)->sig);
4752
}
4853
}
4954
}
5055

56+
void visitFunction(Function* func) {
57+
// Generate dynCalls for invokes
58+
if (func->imported() && func->base.startsWith("invoke_")) {
59+
Signature sig = func->sig;
60+
std::vector<Type> newParams(sig.params.begin() + 1, sig.params.end());
61+
invokeSigs.insert(Signature(Type(newParams), sig.results));
62+
}
63+
}
64+
65+
void generateDynCallThunk(Signature sig);
66+
5167
bool onlyI64;
68+
// The set of all invokes' signatures
69+
std::unordered_set<Signature> invokeSigs;
5270
};
5371

72+
static bool hasI64(Signature sig) {
73+
// We only generate dynCall functions for signatures that contain i64. This is
74+
// because any other function can be called directly from JavaScript using the
75+
// wasm table.
76+
for (auto t : sig.results) {
77+
if (t.getID() == Type::i64) {
78+
return true;
79+
}
80+
}
81+
for (auto t : sig.params) {
82+
if (t.getID() == Type::i64) {
83+
return true;
84+
}
85+
}
86+
return false;
87+
}
88+
89+
static void exportFunction(Module& wasm, Name name, bool must_export) {
90+
if (!wasm.getFunctionOrNull(name)) {
91+
assert(!must_export);
92+
return;
93+
}
94+
if (wasm.getExportOrNull(name)) {
95+
return; // Already exported
96+
}
97+
auto exp = new Export;
98+
exp->name = exp->value = name;
99+
exp->kind = ExternalKind::Function;
100+
wasm.addExport(exp);
101+
}
102+
103+
void GenerateDynCalls::generateDynCallThunk(Signature sig) {
104+
if (onlyI64 && !hasI64(sig)) {
105+
return;
106+
}
107+
108+
Module* wasm = getModule();
109+
Builder builder(*wasm);
110+
Name name = std::string("dynCall_") + getSig(sig.results, sig.params);
111+
if (wasm->getFunctionOrNull(name) || wasm->getExportOrNull(name)) {
112+
return; // module already contains this dyncall
113+
}
114+
std::vector<NameType> params;
115+
params.emplace_back("fptr", Type::i32); // function pointer param
116+
int p = 0;
117+
for (const auto& param : sig.params) {
118+
params.emplace_back(std::to_string(p++), param);
119+
}
120+
Function* f = builder.makeFunction(name, std::move(params), sig.results, {});
121+
Expression* fptr = builder.makeLocalGet(0, Type::i32);
122+
std::vector<Expression*> args;
123+
Index i = 0;
124+
for (const auto& param : sig.params) {
125+
args.push_back(builder.makeLocalGet(++i, param));
126+
}
127+
Expression* call = builder.makeCallIndirect(fptr, args, sig);
128+
f->body = call;
129+
130+
wasm->addFunction(f);
131+
exportFunction(*wasm, f->name, true);
132+
}
133+
54134
Pass* createGenerateDynCallsPass() { return new GenerateDynCalls(false); }
55135
Pass* createGenerateI64DynCallsPass() { return new GenerateDynCalls(true); }
56136

src/passes/PostEmscripten.cpp

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,31 +78,13 @@ struct OptimizeCalls : public WalkerPass<PostWalker<OptimizeCalls>> {
7878

7979
struct PostEmscripten : public Pass {
8080
void run(PassRunner* runner, Module* module) override {
81-
// Optimize imports
82-
optimizeImports(runner, module);
83-
8481
// Optimize calls
8582
OptimizeCalls().run(runner, module);
8683

8784
// Optimize exceptions
8885
optimizeExceptions(runner, module);
8986
}
9087

91-
void optimizeImports(PassRunner* runner, Module* module) {
92-
// Calling emscripten_longjmp_jmpbuf is the same as emscripten_longjmp.
93-
Name EMSCRIPTEN_LONGJMP("emscripten_longjmp");
94-
Name EMSCRIPTEN_LONGJMP_JMPBUF("emscripten_longjmp_jmpbuf");
95-
ImportInfo info(*module);
96-
auto* emscripten_longjmp =
97-
info.getImportedFunction(ENV, EMSCRIPTEN_LONGJMP);
98-
auto* emscripten_longjmp_jmpbuf =
99-
info.getImportedFunction(ENV, EMSCRIPTEN_LONGJMP_JMPBUF);
100-
if (emscripten_longjmp && emscripten_longjmp_jmpbuf) {
101-
// Both exist, so it is worth renaming so that we have only one.
102-
emscripten_longjmp_jmpbuf->base = EMSCRIPTEN_LONGJMP;
103-
}
104-
}
105-
10688
// Optimize exceptions (and setjmp) by removing unnecessary invoke* calls.
10789
// An invoke is a call to JS with a function pointer; JS does a try-catch
10890
// and calls the pointer, catching and reporting any error. If we know no

src/tools/wasm-emscripten-finalize.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,6 @@ int main(int argc, const char* argv[]) {
259259
generator.onlyI64DynCalls = onlyI64DynCalls;
260260
generator.noDynCalls = noDynCalls;
261261

262-
generator.fixInvokeFunctionNames();
263-
264262
std::vector<Name> initializerFunctions;
265263

266264
// The wasm backend emits "__indirect_function_table" as the import name for

src/wasm-emscripten.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,6 @@ class EmscriptenGlueGenerator {
5858
// the file).
5959
void separateDataSegments(Output* outfile, Address base);
6060

61-
void generateDynCallThunk(Signature sig);
62-
6361
bool standalone = false;
6462
bool sideModule = false;
6563
bool minimizeWasmChanges = false;
@@ -71,9 +69,6 @@ class EmscriptenGlueGenerator {
7169
Builder builder;
7270
Address stackPointerOffset;
7371
bool useStackPointerGlobal;
74-
// Used by generateDynCallThunk to track all the dynCall functions created
75-
// so far.
76-
std::unordered_set<Signature> sigs;
7772
};
7873

7974
} // namespace wasm

src/wasm/wasm-emscripten.cpp

Lines changed: 0 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -116,68 +116,6 @@ void EmscriptenGlueGenerator::generatePostInstantiateFunction() {
116116
wasm.addExport(ex);
117117
}
118118

119-
inline void exportFunction(Module& wasm, Name name, bool must_export) {
120-
if (!wasm.getFunctionOrNull(name)) {
121-
assert(!must_export);
122-
return;
123-
}
124-
if (wasm.getExportOrNull(name)) {
125-
return; // Already exported
126-
}
127-
auto exp = new Export;
128-
exp->name = exp->value = name;
129-
exp->kind = ExternalKind::Function;
130-
wasm.addExport(exp);
131-
}
132-
133-
static bool hasI64(Signature sig) {
134-
// We only generate dynCall functions for signatures that contain
135-
// i64. This is because any other function can be called directly
136-
// from JavaScript using the wasm table.
137-
for (auto t : sig.results) {
138-
if (t.getID() == Type::i64) {
139-
return true;
140-
}
141-
}
142-
for (auto t : sig.params) {
143-
if (t.getID() == Type::i64) {
144-
return true;
145-
}
146-
}
147-
return false;
148-
}
149-
150-
void EmscriptenGlueGenerator::generateDynCallThunk(Signature sig) {
151-
if (noDynCalls || (onlyI64DynCalls && !hasI64(sig))) {
152-
return;
153-
}
154-
if (!sigs.insert(sig).second) {
155-
return; // sig is already in the set
156-
}
157-
Name name = std::string("dynCall_") + getSig(sig.results, sig.params);
158-
if (wasm.getFunctionOrNull(name) || wasm.getExportOrNull(name)) {
159-
return; // module already contains this dyncall
160-
}
161-
std::vector<NameType> params;
162-
params.emplace_back("fptr", Type::i32); // function pointer param
163-
int p = 0;
164-
for (const auto& param : sig.params) {
165-
params.emplace_back(std::to_string(p++), param);
166-
}
167-
Function* f = builder.makeFunction(name, std::move(params), sig.results, {});
168-
Expression* fptr = builder.makeLocalGet(0, Type::i32);
169-
std::vector<Expression*> args;
170-
Index i = 0;
171-
for (const auto& param : sig.params) {
172-
args.push_back(builder.makeLocalGet(++i, param));
173-
}
174-
Expression* call = builder.makeCallIndirect(fptr, args, sig);
175-
f->body = call;
176-
177-
wasm.addFunction(f);
178-
exportFunction(wasm, f->name, true);
179-
}
180-
181119
// lld can sometimes produce a build with an imported mutable __stack_pointer
182120
// (i.e. when linking with -fpie). This method internalizes the
183121
// __stack_pointer and initializes it from an immutable global instead.
@@ -549,138 +487,6 @@ EmJsWalker fixEmJsFuncsAndReturnWalker(Module& wasm) {
549487
return walker;
550488
}
551489

552-
// Fixes function name hacks caused by LLVM exception & setjmp/longjmp
553-
// handling pass for wasm.
554-
// This does two things:
555-
// 1. Change emscripten_longjmp_jmpbuf to emscripten_longjmp.
556-
// In setjmp/longjmp handling pass in wasm backend, what we want to do is
557-
// to change all function calls to longjmp to calls to emscripten_longjmp.
558-
// Because we replace all calls to longjmp to emscripten_longjmp, the
559-
// signature of that function should be the same as longjmp:
560-
// emscripten_longjmp(jmp_buf, int)
561-
// But after calling a function that might longjmp, while we test whether
562-
// a longjmp occurred, we have to load an int address value and call
563-
// emscripten_longjmp again with that address as the first argument. (Refer
564-
// to lib/Target/WebAssembly/WebAssemblyEmscriptenEHSjLj.cpp in LLVM for
565-
// details.)
566-
// In this case we need the signature of emscripten_longjmp to be (int,
567-
// int). So we need two different kinds of emscripten_longjmp signatures in
568-
// LLVM IR. Both signatures will be lowered to (int, int) eventually, but
569-
// in LLVM IR, types are not lowered yet.
570-
// So we declare two functions in LLVM:
571-
// emscripten_longjmp_jmpbuf(jmp_buf, int)
572-
// emscripten_longjmp(int, int)
573-
// And we change the name of emscripten_longjmp_jmpbuf to
574-
// emscripten_longjmp here.
575-
// 2. Converts invoke wrapper names.
576-
// Refer to the comments in fixEmExceptionInvoke below.
577-
struct FixInvokeFunctionNamesWalker
578-
: public PostWalker<FixInvokeFunctionNamesWalker> {
579-
Module& wasm;
580-
std::vector<Name> toRemove;
581-
std::map<Name, Name> importRenames;
582-
std::map<Name, Name> functionRenames;
583-
std::set<Signature> invokeSigs;
584-
ImportInfo imports;
585-
586-
FixInvokeFunctionNamesWalker(Module& _wasm) : wasm(_wasm), imports(wasm) {}
587-
588-
// Converts invoke wrapper names generated by LLVM backend to real invoke
589-
// wrapper names that are expected by JavaScript glue code.
590-
// This is required to support wasm exception handling (asm.js style).
591-
//
592-
// LLVM backend lowers
593-
// invoke @func(arg1, arg2) to label %invoke.cont unwind label %lpad
594-
// into
595-
// ... (some code)
596-
// call @invoke_SIG(func, arg1, arg2)
597-
// ... (some code)
598-
// SIG is a mangled string generated based on the LLVM IR-level function
599-
// signature. In LLVM IR, types are not lowered yet, so this mangling scheme
600-
// simply takes LLVM's string representtion of parameter types and concatenate
601-
// them with '_'. For example, the name of an invoke wrapper for function
602-
// void foo(struct mystruct*, int) will be
603-
// "__invoke_void_%struct.mystruct*_int".
604-
// This function converts the names of invoke wrappers based on their lowered
605-
// argument types and a return type. In the example above, the resulting new
606-
// wrapper name becomes "invoke_vii".
607-
Name fixEmExceptionInvoke(const Name& name, Signature sig) {
608-
std::string nameStr = name.c_str();
609-
if (nameStr.front() == '"' && nameStr.back() == '"') {
610-
nameStr = nameStr.substr(1, nameStr.size() - 2);
611-
}
612-
if (nameStr.find("__invoke_") != 0) {
613-
return name;
614-
}
615-
616-
std::vector<Type> newParams(sig.params.begin() + 1, sig.params.end());
617-
Signature sigWoOrigFunc = Signature(Type(newParams), sig.results);
618-
invokeSigs.insert(sigWoOrigFunc);
619-
return Name("invoke_" +
620-
getSig(sigWoOrigFunc.results, sigWoOrigFunc.params));
621-
}
622-
623-
void visitFunction(Function* curr) {
624-
if (!curr->imported()) {
625-
return;
626-
}
627-
628-
Name newname = fixEmExceptionInvoke(curr->base, curr->sig);
629-
if (newname == curr->base) {
630-
return;
631-
}
632-
633-
BYN_TRACE("renaming import: " << curr->module << "." << curr->base << " ("
634-
<< curr->name << ") -> " << newname << "\n");
635-
636-
if (auto* f = imports.getImportedFunction(curr->module, newname)) {
637-
BYN_TRACE("remove redundant import: " << curr->base << "\n");
638-
toRemove.push_back(curr->name);
639-
// Make sure the existing import has the correct internal name.
640-
if (f->name != newname) {
641-
functionRenames[f->name] = newname;
642-
}
643-
} else {
644-
BYN_TRACE("rename import: " << curr->base << "\n");
645-
curr->base = newname;
646-
}
647-
648-
functionRenames[curr->name] = newname;
649-
650-
// Ensure that an imported functions of this name exists.
651-
importRenames[curr->base] = newname;
652-
}
653-
654-
void visitModule(Module* curr) {
655-
for (auto name : toRemove) {
656-
wasm.removeFunction(name);
657-
}
658-
659-
// Rename all uses of the old function to the new import name
660-
ModuleUtils::renameFunctions(wasm, functionRenames);
661-
662-
// For imports that for renamed, update any associated GOT.func imports.
663-
for (auto& pair : importRenames) {
664-
BYN_TRACE("looking for: GOT.func." << pair.first << "\n");
665-
if (auto g = imports.getImportedGlobal("GOT.func", pair.first)) {
666-
BYN_TRACE("renaming corresponding GOT entry: " << g->base << " -> "
667-
<< pair.second << "\n");
668-
g->base = pair.second;
669-
}
670-
}
671-
}
672-
};
673-
674-
void EmscriptenGlueGenerator::fixInvokeFunctionNames() {
675-
BYN_TRACE("fixInvokeFunctionNames\n");
676-
FixInvokeFunctionNamesWalker walker(wasm);
677-
walker.walkModule(&wasm);
678-
BYN_TRACE("generating dyncall thunks\n");
679-
for (auto sig : walker.invokeSigs) {
680-
generateDynCallThunk(sig);
681-
}
682-
}
683-
684490
void printSignatures(std::ostream& o, const std::set<Signature>& c) {
685491
o << "[";
686492
bool first = true;

0 commit comments

Comments
 (0)