Skip to content

Commit 3bba91f

Browse files
committed
[WebAssembly] Rename Emscripten EH functions
Renaming for some Emscripten EH functions has so far been done in wasm-emscripten-finalize tool in Binaryen. But recently we decided to make a compilation/linking path that does not rely on wasm-emscripten-finalize for modifications, so here we move that functionality to LLVM. Invoke wrappers are generated in LowerEmscriptenEHSjLj pass, but final wasm types are not available in the IR pass, we need to rename them at the end of the pipeline. This patch also removes uses of `emscripten_longjmp_jmpbuf` in LowerEmscriptenEHSjLj pass, replacing that with `emscripten_longjmp`. `emscripten_longjmp_jmpbuf` is lowered to `emscripten_longjmp`, but previously we generated calls to `emscripten_longjmp_jmpbuf` in LowerEmscriptenEHSjLj pass because it takes `jmp_buf*` instead of `i32`. But we were able use `ptrtoint` to make it use `emscripten_longjmp` directly here. Addresses: WebAssembly/binaryen#3043 WebAssembly/binaryen#3081 Companions: WebAssembly/binaryen#3191 emscripten-core/emscripten#12399 Reviewed By: dschuff, tlively, sbc100 Differential Revision: https://reviews.llvm.org/D88697
1 parent 91a98ec commit 3bba91f

10 files changed

+310
-89
lines changed

llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp

Lines changed: 111 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ using namespace llvm;
4949
#define DEBUG_TYPE "asm-printer"
5050

5151
extern cl::opt<bool> WasmKeepRegisters;
52+
extern cl::opt<bool> EnableEmException;
53+
extern cl::opt<bool> EnableEmSjLj;
5254

5355
//===----------------------------------------------------------------------===//
5456
// Helpers.
@@ -81,10 +83,91 @@ WebAssemblyTargetStreamer *WebAssemblyAsmPrinter::getTargetStreamer() {
8183
return static_cast<WebAssemblyTargetStreamer *>(TS);
8284
}
8385

86+
// Emscripten exception handling helpers
87+
//
88+
// This converts invoke names generated by LowerEmscriptenEHSjLj to real names
89+
// that are expected by JavaScript glue code. The invoke names generated by
90+
// Emscripten JS glue code are based on their argument and return types; for
91+
// example, for a function that takes an i32 and returns nothing, it is
92+
// 'invoke_vi'. But the format of invoke generated by LowerEmscriptenEHSjLj pass
93+
// contains a mangled string generated from their IR types, for example,
94+
// "__invoke_void_%struct.mystruct*_int", because final wasm types are not
95+
// available in the IR pass. So we convert those names to the form that
96+
// Emscripten JS code expects.
97+
//
98+
// Refer to LowerEmscriptenEHSjLj pass for more details.
99+
100+
// Returns true if the given function name is an invoke name generated by
101+
// LowerEmscriptenEHSjLj pass.
102+
static bool isEmscriptenInvokeName(StringRef Name) {
103+
if (Name.front() == '"' && Name.back() == '"')
104+
Name = Name.substr(1, Name.size() - 2);
105+
return Name.startswith("__invoke_");
106+
}
107+
108+
// Returns a character that represents the given wasm value type in invoke
109+
// signatures.
110+
static char getInvokeSig(wasm::ValType VT) {
111+
switch (VT) {
112+
case wasm::ValType::I32:
113+
return 'i';
114+
case wasm::ValType::I64:
115+
return 'j';
116+
case wasm::ValType::F32:
117+
return 'f';
118+
case wasm::ValType::F64:
119+
return 'd';
120+
case wasm::ValType::V128:
121+
return 'V';
122+
case wasm::ValType::EXNREF:
123+
return 'E';
124+
case wasm::ValType::EXTERNREF:
125+
return 'X';
126+
}
127+
}
128+
129+
// Given the wasm signature, generate the invoke name in the format JS glue code
130+
// expects.
131+
static std::string getEmscriptenInvokeSymbolName(wasm::WasmSignature *Sig) {
132+
assert(Sig->Returns.size() <= 1);
133+
std::string Ret = "invoke_";
134+
if (!Sig->Returns.empty())
135+
for (auto VT : Sig->Returns)
136+
Ret += getInvokeSig(VT);
137+
else
138+
Ret += 'v';
139+
// Invokes' first argument is a pointer to the original function, so skip it
140+
for (unsigned I = 1, E = Sig->Params.size(); I < E; I++)
141+
Ret += getInvokeSig(Sig->Params[I]);
142+
return Ret;
143+
}
144+
84145
//===----------------------------------------------------------------------===//
85146
// WebAssemblyAsmPrinter Implementation.
86147
//===----------------------------------------------------------------------===//
87148

149+
MCSymbolWasm *WebAssemblyAsmPrinter::getMCSymbolForFunction(
150+
const Function *F, bool EnableEmEH, wasm::WasmSignature *Sig,
151+
bool &InvokeDetected) {
152+
MCSymbolWasm *WasmSym = nullptr;
153+
if (EnableEmEH && isEmscriptenInvokeName(F->getName())) {
154+
assert(Sig);
155+
InvokeDetected = true;
156+
if (Sig->Returns.size() > 1) {
157+
std::string Msg =
158+
"Emscripten EH/SjLj does not support multivalue returns: " +
159+
std::string(F->getName()) + ": " +
160+
WebAssembly::signatureToString(Sig);
161+
report_fatal_error(Msg);
162+
}
163+
WasmSym = cast<MCSymbolWasm>(
164+
GetExternalSymbolSymbol(getEmscriptenInvokeSymbolName(Sig)));
165+
} else {
166+
WasmSym = cast<MCSymbolWasm>(getSymbol(F));
167+
}
168+
return WasmSym;
169+
}
170+
88171
void WebAssemblyAsmPrinter::emitEndOfAsmFile(Module &M) {
89172
for (auto &It : OutContext.getSymbols()) {
90173
// Emit a .globaltype and .eventtype declaration.
@@ -95,6 +178,7 @@ void WebAssemblyAsmPrinter::emitEndOfAsmFile(Module &M) {
95178
getTargetStreamer()->emitEventType(Sym);
96179
}
97180

181+
DenseSet<MCSymbol *> InvokeSymbols;
98182
for (const auto &F : M) {
99183
if (F.isIntrinsic())
100184
continue;
@@ -104,31 +188,46 @@ void WebAssemblyAsmPrinter::emitEndOfAsmFile(Module &M) {
104188
SmallVector<MVT, 4> Results;
105189
SmallVector<MVT, 4> Params;
106190
computeSignatureVTs(F.getFunctionType(), &F, F, TM, Params, Results);
107-
auto *Sym = cast<MCSymbolWasm>(getSymbol(&F));
191+
// At this point these MCSymbols may or may not have been created already
192+
// and thus also contain a signature, but we need to get the signature
193+
// anyway here in case it is an invoke that has not yet been created. We
194+
// will discard it later if it turns out not to be necessary.
195+
auto Signature = signatureFromMVTs(Results, Params);
196+
bool InvokeDetected = false;
197+
auto *Sym = getMCSymbolForFunction(&F, EnableEmException || EnableEmSjLj,
198+
Signature.get(), InvokeDetected);
199+
200+
// Multiple functions can be mapped to the same invoke symbol. For
201+
// example, two IR functions '__invoke_void_i8*' and '__invoke_void_i32'
202+
// are both mapped to '__invoke_vi'. We keep them in a set once we emit an
203+
// Emscripten EH symbol so we don't emit the same symbol twice.
204+
if (InvokeDetected && !InvokeSymbols.insert(Sym).second)
205+
continue;
206+
108207
Sym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION);
109208
if (!Sym->getSignature()) {
110-
auto Signature = signatureFromMVTs(Results, Params);
111209
Sym->setSignature(Signature.get());
112210
addSignature(std::move(Signature));
211+
} else {
212+
// This symbol has already been created and had a signature. Discard it.
213+
Signature.reset();
113214
}
114-
// FIXME: this was originally intended for post-linking and was only used
115-
// for imports that were only called indirectly (i.e. s2wasm could not
116-
// infer the type from a call). With object files it applies to all
117-
// imports. so fix the names and the tests, or rethink how import
118-
// delcarations work in asm files.
215+
119216
getTargetStreamer()->emitFunctionType(Sym);
120217

121-
if (TM.getTargetTriple().isOSBinFormatWasm() &&
122-
F.hasFnAttribute("wasm-import-module")) {
218+
if (F.hasFnAttribute("wasm-import-module")) {
123219
StringRef Name =
124220
F.getFnAttribute("wasm-import-module").getValueAsString();
125221
Sym->setImportModule(storeName(Name));
126222
getTargetStreamer()->emitImportModule(Sym, Name);
127223
}
128-
if (TM.getTargetTriple().isOSBinFormatWasm() &&
129-
F.hasFnAttribute("wasm-import-name")) {
224+
if (F.hasFnAttribute("wasm-import-name")) {
225+
// If this is a converted Emscripten EH/SjLj symbol, we shouldn't use
226+
// the original function name but the converted symbol name.
130227
StringRef Name =
131-
F.getFnAttribute("wasm-import-name").getValueAsString();
228+
InvokeDetected
229+
? Sym->getName()
230+
: F.getFnAttribute("wasm-import-name").getValueAsString();
132231
Sym->setImportName(storeName(Name));
133232
getTargetStreamer()->emitImportName(Sym, Name);
134233
}
@@ -304,7 +403,6 @@ void WebAssemblyAsmPrinter::emitFunctionBodyStart() {
304403
addSignature(std::move(Signature));
305404
WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION);
306405

307-
// FIXME: clean up how params and results are emitted (use signatures)
308406
getTargetStreamer()->emitFunctionType(WasmSym);
309407

310408
// Emit the function index.

llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ class LLVM_LIBRARY_VISIBILITY WebAssemblyAsmPrinter final : public AsmPrinter {
7777
MVT getRegType(unsigned RegNo) const;
7878
std::string regToString(const MachineOperand &MO);
7979
WebAssemblyTargetStreamer *getTargetStreamer();
80+
MCSymbolWasm *getMCSymbolForFunction(const Function *F, bool EnableEmEH,
81+
wasm::WasmSignature *Sig,
82+
bool &InvokeDetected);
8083
};
8184

8285
} // end namespace llvm

llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,7 @@
140140
/// 1) Lower
141141
/// longjmp(buf, value)
142142
/// into
143-
/// emscripten_longjmp_jmpbuf(buf, value)
144-
/// emscripten_longjmp_jmpbuf will be lowered to emscripten_longjmp later.
143+
/// emscripten_longjmp(buf, value)
145144
///
146145
/// In case calls to setjmp() exists
147146
///
@@ -196,14 +195,9 @@
196195
/// stored in saveSetjmp. testSetjmp returns a setjmp label, a unique ID to
197196
/// each setjmp callsite. Label 0 means this longjmp buffer does not
198197
/// correspond to one of the setjmp callsites in this function, so in this
199-
/// case we just chain the longjmp to the caller. (Here we call
200-
/// emscripten_longjmp, which is different from emscripten_longjmp_jmpbuf.
201-
/// emscripten_longjmp_jmpbuf takes jmp_buf as its first argument, while
202-
/// emscripten_longjmp takes an int. Both of them will eventually be lowered
203-
/// to emscripten_longjmp in s2wasm, but here we need two signatures - we
204-
/// can't translate an int value to a jmp_buf.)
205-
/// Label -1 means no longjmp occurred. Otherwise we jump to the right
206-
/// post-setjmp BB based on the label.
198+
/// case we just chain the longjmp to the caller. Label -1 means no longjmp
199+
/// occurred. Otherwise we jump to the right post-setjmp BB based on the
200+
/// label.
207201
///
208202
///===----------------------------------------------------------------------===//
209203

@@ -241,7 +235,6 @@ class WebAssemblyLowerEmscriptenEHSjLj final : public ModulePass {
241235
Function *ResumeF = nullptr;
242236
Function *EHTypeIDF = nullptr;
243237
Function *EmLongjmpF = nullptr;
244-
Function *EmLongjmpJmpbufF = nullptr;
245238
Function *SaveSetjmpF = nullptr;
246239
Function *TestSetjmpF = nullptr;
247240

@@ -642,6 +635,30 @@ void WebAssemblyLowerEmscriptenEHSjLj::rebuildSSA(Function &F) {
642635
}
643636
}
644637

638+
// Replace uses of longjmp with emscripten_longjmp. emscripten_longjmp takes
639+
// arguments of type {i32, i32} and longjmp takes {jmp_buf*, i32}, so we need a
640+
// ptrtoint instruction here to make the type match. jmp_buf* will eventually be
641+
// lowered to i32 in the wasm backend.
642+
static void replaceLongjmpWithEmscriptenLongjmp(Function *LongjmpF,
643+
Function *EmLongjmpF) {
644+
SmallVector<CallInst *, 8> ToErase;
645+
LLVMContext &C = LongjmpF->getParent()->getContext();
646+
IRBuilder<> IRB(C);
647+
for (User *U : LongjmpF->users()) {
648+
auto *CI = dyn_cast<CallInst>(U);
649+
if (!CI)
650+
report_fatal_error("Does not support indirect calls to longjmp");
651+
IRB.SetInsertPoint(CI);
652+
Value *Jmpbuf =
653+
IRB.CreatePtrToInt(CI->getArgOperand(0), IRB.getInt32Ty(), "jmpbuf");
654+
IRB.CreateCall(EmLongjmpF, {Jmpbuf, CI->getArgOperand(1)});
655+
ToErase.push_back(CI);
656+
}
657+
658+
for (auto *I : ToErase)
659+
I->eraseFromParent();
660+
}
661+
645662
bool WebAssemblyLowerEmscriptenEHSjLj::runOnModule(Module &M) {
646663
LLVM_DEBUG(dbgs() << "********** Lower Emscripten EH & SjLj **********\n");
647664

@@ -654,6 +671,10 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runOnModule(Module &M) {
654671
bool LongjmpUsed = LongjmpF && !LongjmpF->use_empty();
655672
bool DoSjLj = EnableSjLj && (SetjmpUsed || LongjmpUsed);
656673

674+
if ((EnableEH || DoSjLj) &&
675+
Triple(M.getTargetTriple()).getArch() == Triple::wasm64)
676+
report_fatal_error("Emscripten EH/SjLj is not supported with wasm64 yet");
677+
657678
auto *TPC = getAnalysisIfAvailable<TargetPassConfig>();
658679
assert(TPC && "Expected a TargetPassConfig");
659680
auto &TM = TPC->getTM<WebAssemblyTargetMachine>();
@@ -696,22 +717,21 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runOnModule(Module &M) {
696717
if (DoSjLj) {
697718
Changed = true; // We have setjmp or longjmp somewhere
698719

699-
if (LongjmpF) {
700-
// Replace all uses of longjmp with emscripten_longjmp_jmpbuf, which is
701-
// defined in JS code
702-
EmLongjmpJmpbufF = getEmscriptenFunction(LongjmpF->getFunctionType(),
703-
"emscripten_longjmp_jmpbuf", &M);
704-
LongjmpF->replaceAllUsesWith(EmLongjmpJmpbufF);
705-
}
720+
// Register emscripten_longjmp function
721+
FunctionType *FTy = FunctionType::get(
722+
IRB.getVoidTy(), {IRB.getInt32Ty(), IRB.getInt32Ty()}, false);
723+
EmLongjmpF = getEmscriptenFunction(FTy, "emscripten_longjmp", &M);
724+
725+
if (LongjmpF)
726+
replaceLongjmpWithEmscriptenLongjmp(LongjmpF, EmLongjmpF);
706727

707728
if (SetjmpF) {
708729
// Register saveSetjmp function
709730
FunctionType *SetjmpFTy = SetjmpF->getFunctionType();
710-
FunctionType *FTy =
711-
FunctionType::get(Type::getInt32PtrTy(C),
712-
{SetjmpFTy->getParamType(0), IRB.getInt32Ty(),
713-
Type::getInt32PtrTy(C), IRB.getInt32Ty()},
714-
false);
731+
FTy = FunctionType::get(Type::getInt32PtrTy(C),
732+
{SetjmpFTy->getParamType(0), IRB.getInt32Ty(),
733+
Type::getInt32PtrTy(C), IRB.getInt32Ty()},
734+
false);
715735
SaveSetjmpF = getEmscriptenFunction(FTy, "saveSetjmp", &M);
716736

717737
// Register testSetjmp function
@@ -720,10 +740,6 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runOnModule(Module &M) {
720740
{IRB.getInt32Ty(), Type::getInt32PtrTy(C), IRB.getInt32Ty()}, false);
721741
TestSetjmpF = getEmscriptenFunction(FTy, "testSetjmp", &M);
722742

723-
FTy = FunctionType::get(IRB.getVoidTy(),
724-
{IRB.getInt32Ty(), IRB.getInt32Ty()}, false);
725-
EmLongjmpF = getEmscriptenFunction(FTy, "emscripten_longjmp", &M);
726-
727743
// Only traverse functions that uses setjmp in order not to insert
728744
// unnecessary prep / cleanup code in every function
729745
SmallPtrSet<Function *, 8> SetjmpUsers;

llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,29 +38,34 @@ cl::opt<bool>
3838
" instruction output for test purposes only."),
3939
cl::init(false));
4040

41+
extern cl::opt<bool> EnableEmException;
42+
extern cl::opt<bool> EnableEmSjLj;
43+
4144
static void removeRegisterOperands(const MachineInstr *MI, MCInst &OutMI);
4245

4346
MCSymbol *
4447
WebAssemblyMCInstLower::GetGlobalAddressSymbol(const MachineOperand &MO) const {
4548
const GlobalValue *Global = MO.getGlobal();
46-
auto *WasmSym = cast<MCSymbolWasm>(Printer.getSymbol(Global));
47-
48-
if (const auto *FuncTy = dyn_cast<FunctionType>(Global->getValueType())) {
49-
const MachineFunction &MF = *MO.getParent()->getParent()->getParent();
50-
const TargetMachine &TM = MF.getTarget();
51-
const Function &CurrentFunc = MF.getFunction();
52-
53-
SmallVector<MVT, 1> ResultMVTs;
54-
SmallVector<MVT, 4> ParamMVTs;
55-
const auto *const F = dyn_cast<Function>(Global);
56-
computeSignatureVTs(FuncTy, F, CurrentFunc, TM, ParamMVTs, ResultMVTs);
57-
58-
auto Signature = signatureFromMVTs(ResultMVTs, ParamMVTs);
59-
WasmSym->setSignature(Signature.get());
60-
Printer.addSignature(std::move(Signature));
61-
WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION);
62-
}
63-
49+
if (!isa<Function>(Global))
50+
return cast<MCSymbolWasm>(Printer.getSymbol(Global));
51+
52+
const auto *FuncTy = cast<FunctionType>(Global->getValueType());
53+
const MachineFunction &MF = *MO.getParent()->getParent()->getParent();
54+
const TargetMachine &TM = MF.getTarget();
55+
const Function &CurrentFunc = MF.getFunction();
56+
57+
SmallVector<MVT, 1> ResultMVTs;
58+
SmallVector<MVT, 4> ParamMVTs;
59+
const auto *const F = dyn_cast<Function>(Global);
60+
computeSignatureVTs(FuncTy, F, CurrentFunc, TM, ParamMVTs, ResultMVTs);
61+
auto Signature = signatureFromMVTs(ResultMVTs, ParamMVTs);
62+
63+
bool InvokeDetected = false;
64+
auto *WasmSym = Printer.getMCSymbolForFunction(
65+
F, EnableEmException || EnableEmSjLj, Signature.get(), InvokeDetected);
66+
WasmSym->setSignature(Signature.get());
67+
Printer.addSignature(std::move(Signature));
68+
WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION);
6469
return WasmSym;
6570
}
6671

llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ using namespace llvm;
3434
#define DEBUG_TYPE "wasm"
3535

3636
// Emscripten's asm.js-style exception handling
37-
static cl::opt<bool> EnableEmException(
37+
cl::opt<bool> EnableEmException(
3838
"enable-emscripten-cxx-exceptions",
3939
cl::desc("WebAssembly Emscripten-style exception handling"),
4040
cl::init(false));
4141

4242
// Emscripten's asm.js-style setjmp/longjmp handling
43-
static cl::opt<bool> EnableEmSjLj(
43+
cl::opt<bool> EnableEmSjLj(
4444
"enable-emscripten-sjlj",
4545
cl::desc("WebAssembly Emscripten-style setjmp/longjmp handling"),
4646
cl::init(false));

llvm/test/CodeGen/WebAssembly/function-bitcasts.ll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,12 @@ define void @test_argument() {
153153
; CHECK-LABEL: test_invoke:
154154
; CHECK: i32.const $push[[L1:[0-9]+]]=, call_func{{$}}
155155
; CHECK-NEXT: i32.const $push[[L0:[0-9]+]]=, has_i32_ret{{$}}
156-
; CHECK-NEXT: call "__invoke_void_i32()*", $pop[[L1]], $pop[[L0]]{{$}}
156+
; CHECK-NEXT: call invoke_vi, $pop[[L1]], $pop[[L0]]{{$}}
157157
; CHECK: i32.const $push[[L3:[0-9]+]]=, call_func{{$}}
158158
; CHECK-NEXT: i32.const $push[[L2:[0-9]+]]=, has_i32_arg{{$}}
159-
; CHECK-NEXT: call "__invoke_void_i32()*", $pop[[L3]], $pop[[L2]]{{$}}
159+
; CHECK-NEXT: call invoke_vi, $pop[[L3]], $pop[[L2]]{{$}}
160160
; CHECK: i32.const $push[[L4:[0-9]+]]=, .Lhas_i32_arg_bitcast.2{{$}}
161-
; CHECK-NEXT: call __invoke_void, $pop[[L4]]{{$}}
161+
; CHECK-NEXT: call invoke_v, $pop[[L4]]{{$}}
162162
declare i32 @personality(...)
163163
define void @test_invoke() personality i32 (...)* @personality {
164164
entry:

0 commit comments

Comments
 (0)