diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.h b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.h --- a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.h +++ b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.h @@ -77,6 +77,8 @@ MVT getRegType(unsigned RegNo) const; std::string regToString(const MachineOperand &MO); WebAssemblyTargetStreamer *getTargetStreamer(); + MCSymbolWasm *getMCSymbolForFunction(const Function *F, bool EnableEmEH, + wasm::WasmSignature *Sig); }; } // end namespace llvm diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp @@ -49,6 +49,8 @@ #define DEBUG_TYPE "asm-printer" extern cl::opt WasmKeepRegisters; +extern cl::opt EnableEmException; +extern cl::opt EnableEmSjLj; //===----------------------------------------------------------------------===// // Helpers. @@ -81,10 +83,119 @@ return static_cast(TS); } +// Emscripten exception handling helpers +// +// We need to fix two things here: +// +// 1. Change emscripten_longjmp_jmpbuf function to emscripten_longjmp. +// What we try to do in LowerEmscriptenEHSjLj pass is to change all function +// calls to longjmp to calls to emscripten_longjmp. Because we replace all +// calls to longjmp to emscripten_longjmp, the signature of that function +// should be the same as longjmp: emscripten_longjmp(jmp_buf, int) But after +// calling a function that might longjmp, while we test whether a longjmp +// occurred, we have to load an int address value and call emscripten_longjmp +// again with that address as the first argument. In this case we need the +// signature of emscripten_longjmp to be (int, int). We have to give them +// different names in the LLVM IR pass because they have different +// signatures, but after instruction selection they are lowered to the same +// signature, so we unify those names here. +// +// 2. Converts invoke wrapper names generated by LowerEmscriptenEHSjLj to real +// wrapper names that are expected by JavaScript glue code. +// The invoke wrapper names generated by Emscripten JS glue code are based on +// their argument and return types; for example, for a function that takes an +// i32 and returns nothing, it is 'invoke_vi'. But the format of invoke +// wrapper generated by LowerEmscriptenEHSjLj pass contains a mangled string +// generated from their IR types, for example, +// "__invoke_void_%struct.mystruct*_int", because final wasm types are not +// available in the IR pass. So we convert those names to the form that +// Emscripten JS code expects. +// +// Refer to LowerEmscriptenEHSjLj pass for more details. + +// Returns true if the given function name is an invoke wrapper name generated +// by LowerEmscriptenEHSjLj pass. +static bool isEmscriptenInvokeWrapperName(StringRef Name) { + if (Name.front() == '"' && Name.back() == '"') + Name = Name.substr(1, Name.size() - 2); + return Name.startswith("__invoke_"); +} + +// Returns a character that represents the given wasm value type in invoke +// wrapper strings. +static char getInvokeWrapperSig(wasm::ValType VT) { + switch (VT) { + case wasm::ValType::I32: + return 'i'; + case wasm::ValType::I64: + return 'j'; + case wasm::ValType::F32: + return 'f'; + case wasm::ValType::F64: + return 'd'; + case wasm::ValType::V128: + return 'V'; + case wasm::ValType::EXNREF: + return 'E'; + case wasm::ValType::EXTERNREF: + return 'X'; + } +} + +// Given the wasm signature, generate the invoke wrapper name in the format JS +// glue code expects. +static std::string +getEmscriptenInvokeWrapperSymbolName(wasm::WasmSignature *Sig) { + assert(Sig->Returns.size() <= 1); + std::string Ret = "invoke_"; + if (!Sig->Returns.empty()) + for (auto VT : Sig->Returns) + Ret += getInvokeWrapperSig(VT); + else + Ret += 'v'; + // Invoke wrappers' first argument is a pointer to the original function, so + // skip it + for (unsigned I = 1, E = Sig->Params.size(); I < E; I++) + Ret += getInvokeWrapperSig(Sig->Params[I]); + return Ret; +} + +// Returns true if the given symbol name is one of the specially converted +// Emscripten EH / SjLj related names. Note that these are converted final +// symbol names and not the original names generated fom LowerEmscripten EHSjLj +// pass. These include: +// - Invoke wrapper symbol names +// - "emscripten_longjmp" +static bool isEmscriptenEHSymbolName(StringRef Name) { + return Name.startswith("invoke_") || Name == "emscripten_longjmp"; +} + //===----------------------------------------------------------------------===// // WebAssemblyAsmPrinter Implementation. //===----------------------------------------------------------------------===// +MCSymbolWasm *WebAssemblyAsmPrinter::getMCSymbolForFunction( + const Function *F, bool EnableEmEH, wasm::WasmSignature *Sig) { + MCSymbolWasm *WasmSym = nullptr; + if (EnableEmEH && F->getName() == "emscripten_longjmp_jmpbuf") { + WasmSym = cast(GetExternalSymbolSymbol("emscripten_longjmp")); + } else if (EnableEmEH && isEmscriptenInvokeWrapperName(F->getName())) { + assert(Sig); + if (Sig->Returns.size() > 1) { + std::string Msg = + "Emscripten EH/SjLj does not support multivalue returns: " + + std::string(F->getName()) + ": " + + WebAssembly::signatureToString(Sig); + report_fatal_error(Msg); + } + WasmSym = cast( + GetExternalSymbolSymbol(getEmscriptenInvokeWrapperSymbolName(Sig))); + } else { + WasmSym = cast(getSymbol(F)); + } + return WasmSym; +} + void WebAssemblyAsmPrinter::emitEndOfAsmFile(Module &M) { for (auto &It : OutContext.getSymbols()) { // Emit a .globaltype and .eventtype declaration. @@ -95,6 +206,7 @@ getTargetStreamer()->emitEventType(Sym); } + DenseSet EmSymbols; for (const auto &F : M) { if (F.isIntrinsic()) continue; @@ -104,31 +216,48 @@ SmallVector Results; SmallVector Params; computeSignatureVTs(F.getFunctionType(), &F, F, TM, Params, Results); - auto *Sym = cast(getSymbol(&F)); + // At this point these MCSymbols may or may not have been created already + // and thus also contain a signature, but we need to get the signature + // anyway here in case it is an Emscripten EH wrapper that has not yet + // been created. We will discard it later if it turns out not to be + // necessary. + auto Signature = signatureFromMVTs(Results, Params); + auto *Sym = getMCSymbolForFunction(&F, EnableEmException || EnableEmSjLj, + Signature.get()); + + // Multiple functions can be mapped to the same invoke wrapper symbol. For + // example, two IR functions '__invoke_void_i8*' and '__invoke_void_i32' + // are both mapped to '__invoke_vi'. We keep them in a set once we emit an + // Emscripten EH symbol so we don't emit the same symbol twice. + if (isEmscriptenEHSymbolName(Sym->getName())) { + if (!EmSymbols.insert(Sym).second) + continue; + } + Sym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION); if (!Sym->getSignature()) { - auto Signature = signatureFromMVTs(Results, Params); Sym->setSignature(Signature.get()); addSignature(std::move(Signature)); + } else { + // This symbol has already been created and had a signature. Discard it. + Signature.release(); } - // FIXME: this was originally intended for post-linking and was only used - // for imports that were only called indirectly (i.e. s2wasm could not - // infer the type from a call). With object files it applies to all - // imports. so fix the names and the tests, or rethink how import - // delcarations work in asm files. + getTargetStreamer()->emitFunctionType(Sym); - if (TM.getTargetTriple().isOSBinFormatWasm() && - F.hasFnAttribute("wasm-import-module")) { + if (F.hasFnAttribute("wasm-import-module")) { StringRef Name = F.getFnAttribute("wasm-import-module").getValueAsString(); Sym->setImportModule(storeName(Name)); getTargetStreamer()->emitImportModule(Sym, Name); } - if (TM.getTargetTriple().isOSBinFormatWasm() && - F.hasFnAttribute("wasm-import-name")) { + if (F.hasFnAttribute("wasm-import-name")) { + // If this is a converted Emscripten EH/SjLj symbol, we shouldn't use + // the original function name but the converted symbol name. StringRef Name = - F.getFnAttribute("wasm-import-name").getValueAsString(); + isEmscriptenEHSymbolName(Sym->getName()) + ? Sym->getName() + : F.getFnAttribute("wasm-import-name").getValueAsString(); Sym->setImportName(storeName(Name)); getTargetStreamer()->emitImportName(Sym, Name); } @@ -304,7 +433,6 @@ addSignature(std::move(Signature)); WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION); - // FIXME: clean up how params and results are emitted (use signatures) getTargetStreamer()->emitFunctionType(WasmSym); // Emit the function index. diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp @@ -200,8 +200,8 @@ /// emscripten_longjmp, which is different from emscripten_longjmp_jmpbuf. /// emscripten_longjmp_jmpbuf takes jmp_buf as its first argument, while /// emscripten_longjmp takes an int. Both of them will eventually be lowered -/// to emscripten_longjmp in s2wasm, but here we need two signatures - we -/// can't translate an int value to a jmp_buf.) +/// to emscripten_longjmp later, but here we need two signatures - we can't +/// translate an int value to a jmp_buf.) /// Label -1 means no longjmp occurred. Otherwise we jump to the right /// post-setjmp BB based on the label. /// diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp @@ -38,29 +38,33 @@ " instruction output for test purposes only."), cl::init(false)); +extern cl::opt EnableEmException; +extern cl::opt EnableEmSjLj; + static void removeRegisterOperands(const MachineInstr *MI, MCInst &OutMI); MCSymbol * WebAssemblyMCInstLower::GetGlobalAddressSymbol(const MachineOperand &MO) const { const GlobalValue *Global = MO.getGlobal(); - auto *WasmSym = cast(Printer.getSymbol(Global)); - - if (const auto *FuncTy = dyn_cast(Global->getValueType())) { - const MachineFunction &MF = *MO.getParent()->getParent()->getParent(); - const TargetMachine &TM = MF.getTarget(); - const Function &CurrentFunc = MF.getFunction(); - - SmallVector ResultMVTs; - SmallVector ParamMVTs; - const auto *const F = dyn_cast(Global); - computeSignatureVTs(FuncTy, F, CurrentFunc, TM, ParamMVTs, ResultMVTs); - - auto Signature = signatureFromMVTs(ResultMVTs, ParamMVTs); - WasmSym->setSignature(Signature.get()); - Printer.addSignature(std::move(Signature)); - WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION); - } - + if (!isa(Global)) + return cast(Printer.getSymbol(Global)); + + const auto *FuncTy = cast(Global->getValueType()); + const MachineFunction &MF = *MO.getParent()->getParent()->getParent(); + const TargetMachine &TM = MF.getTarget(); + const Function &CurrentFunc = MF.getFunction(); + + SmallVector ResultMVTs; + SmallVector ParamMVTs; + const auto *const F = dyn_cast(Global); + computeSignatureVTs(FuncTy, F, CurrentFunc, TM, ParamMVTs, ResultMVTs); + auto Signature = signatureFromMVTs(ResultMVTs, ParamMVTs); + + auto *WasmSym = Printer.getMCSymbolForFunction( + F, EnableEmException || EnableEmSjLj, Signature.get()); + WasmSym->setSignature(Signature.get()); + Printer.addSignature(std::move(Signature)); + WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION); return WasmSym; } diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp @@ -34,13 +34,13 @@ #define DEBUG_TYPE "wasm" // Emscripten's asm.js-style exception handling -static cl::opt EnableEmException( +cl::opt EnableEmException( "enable-emscripten-cxx-exceptions", cl::desc("WebAssembly Emscripten-style exception handling"), cl::init(false)); // Emscripten's asm.js-style setjmp/longjmp handling -static cl::opt EnableEmSjLj( +cl::opt EnableEmSjLj( "enable-emscripten-sjlj", cl::desc("WebAssembly Emscripten-style setjmp/longjmp handling"), cl::init(false)); diff --git a/llvm/test/CodeGen/WebAssembly/function-bitcasts.ll b/llvm/test/CodeGen/WebAssembly/function-bitcasts.ll --- a/llvm/test/CodeGen/WebAssembly/function-bitcasts.ll +++ b/llvm/test/CodeGen/WebAssembly/function-bitcasts.ll @@ -153,12 +153,12 @@ ; CHECK-LABEL: test_invoke: ; CHECK: i32.const $push[[L1:[0-9]+]]=, call_func{{$}} ; CHECK-NEXT: i32.const $push[[L0:[0-9]+]]=, has_i32_ret{{$}} -; CHECK-NEXT: call "__invoke_void_i32()*", $pop[[L1]], $pop[[L0]]{{$}} +; CHECK-NEXT: call invoke_vi, $pop[[L1]], $pop[[L0]]{{$}} ; CHECK: i32.const $push[[L3:[0-9]+]]=, call_func{{$}} ; CHECK-NEXT: i32.const $push[[L2:[0-9]+]]=, has_i32_arg{{$}} -; CHECK-NEXT: call "__invoke_void_i32()*", $pop[[L3]], $pop[[L2]]{{$}} +; CHECK-NEXT: call invoke_vi, $pop[[L3]], $pop[[L2]]{{$}} ; CHECK: i32.const $push[[L4:[0-9]+]]=, .Lhas_i32_arg_bitcast.2{{$}} -; CHECK-NEXT: call __invoke_void, $pop[[L4]]{{$}} +; CHECK-NEXT: call invoke_v, $pop[[L4]]{{$}} declare i32 @personality(...) define void @test_invoke() personality i32 (...)* @personality { entry: diff --git a/llvm/test/CodeGen/WebAssembly/lower-em-ehsjlj-options.ll b/llvm/test/CodeGen/WebAssembly/lower-em-ehsjlj-multi-return.ll copy from llvm/test/CodeGen/WebAssembly/lower-em-ehsjlj-options.ll copy to llvm/test/CodeGen/WebAssembly/lower-em-ehsjlj-multi-return.ll --- a/llvm/test/CodeGen/WebAssembly/lower-em-ehsjlj-options.ll +++ b/llvm/test/CodeGen/WebAssembly/lower-em-ehsjlj-multi-return.ll @@ -1,27 +1,25 @@ -; RUN: llc < %s -enable-emscripten-cxx-exceptions | FileCheck %s --check-prefix=EH -; RUN: llc < %s -enable-emscripten-sjlj | FileCheck %s --check-prefix=SJLJ -; RUN: llc < %s | FileCheck %s --check-prefix=NONE +; RUN: not --crash llc < %s -enable-emscripten-cxx-exceptions -mattr=+multivalue 2>&1 | FileCheck %s --check-prefix=EH +; RUN: not --crash llc < %s -enable-emscripten-sjlj -mattr=+multivalue 2>&1 | FileCheck %s --check-prefix=SJLJ + +; Currently multivalue returning functions are not supported in Emscripten EH / +; SjLj. Make sure they error out. target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target triple = "wasm32-unknown-unknown" %struct.__jmp_buf_tag = type { [6 x i32], i32, [32 x i32] } -define hidden void @exception() personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { -; EH-LABEL: type exception,@function -; NONE-LABEL: type exception,@function +define void @exception() personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { entry: - invoke void @foo() + invoke {i32, i32} @foo(i32 3) to label %try.cont unwind label %lpad -; EH: call __invoke_void -; NONE: call foo lpad: ; preds = %entry - %0 = landingpad { i8*, i32 } + %1 = landingpad { i8*, i32 } catch i8* null - %1 = extractvalue { i8*, i32 } %0, 0 - %2 = extractvalue { i8*, i32 } %0, 1 - %3 = call i8* @__cxa_begin_catch(i8* %1) #2 + %2 = extractvalue { i8*, i32 } %1, 0 + %3 = extractvalue { i8*, i32 } %1, 1 + %4 = call i8* @__cxa_begin_catch(i8* %2) #2 call void @__cxa_end_catch() br label %try.cont @@ -29,23 +27,18 @@ ret void } -define hidden void @setjmp_longjmp() { -; SJLJ-LABEL: type setjmp_longjmp,@function -; NONE-LABEL: type setjmp_longjmp,@function +define void @setjmp_longjmp() { entry: %buf = alloca [1 x %struct.__jmp_buf_tag], align 16 %arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf, i32 0, i32 0 %call = call i32 @setjmp(%struct.__jmp_buf_tag* %arraydecay) #0 %arraydecay1 = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf, i32 0, i32 0 + call {i32, i32} @foo(i32 3) call void @longjmp(%struct.__jmp_buf_tag* %arraydecay1, i32 1) #1 unreachable -; SJLJ: call saveSetjmp -; SJLJ: call testSetjmp -; NONE: call setjmp -; NONE: call longjmp } -declare void @foo() +declare {i32, i32} @foo(i32) declare i32 @__gxx_personality_v0(...) declare i8* @__cxa_begin_catch(i8*) declare void @__cxa_end_catch() @@ -59,3 +52,6 @@ attributes #0 = { returns_twice } attributes #1 = { noreturn } attributes #2 = { nounwind } + +; EH: LLVM ERROR: Emscripten EH/SjLj does not support multivalue returns +; SJLJ: LLVM ERROR: Emscripten EH/SjLj does not support multivalue returns diff --git a/llvm/test/CodeGen/WebAssembly/lower-em-ehsjlj-options.ll b/llvm/test/CodeGen/WebAssembly/lower-em-ehsjlj-options.ll --- a/llvm/test/CodeGen/WebAssembly/lower-em-ehsjlj-options.ll +++ b/llvm/test/CodeGen/WebAssembly/lower-em-ehsjlj-options.ll @@ -7,14 +7,15 @@ %struct.__jmp_buf_tag = type { [6 x i32], i32, [32 x i32] } -define hidden void @exception() personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { +define void @exception() personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) { ; EH-LABEL: type exception,@function ; NONE-LABEL: type exception,@function entry: - invoke void @foo() + invoke void @foo(i32 3) to label %try.cont unwind label %lpad -; EH: call __invoke_void -; NONE: call foo +; EH: call invoke_vi +; EH-NOT: call __invoke_void_i32 +; NONE: call foo lpad: ; preds = %entry %0 = landingpad { i8*, i32 } @@ -29,7 +30,7 @@ ret void } -define hidden void @setjmp_longjmp() { +define void @setjmp_longjmp() { ; SJLJ-LABEL: type setjmp_longjmp,@function ; NONE-LABEL: type setjmp_longjmp,@function entry: @@ -40,12 +41,17 @@ call void @longjmp(%struct.__jmp_buf_tag* %arraydecay1, i32 1) #1 unreachable ; SJLJ: call saveSetjmp +; SJLJ: i32.const emscripten_longjmp +; SJLJ-NOT: i32.const emscripten_longjmp_jmpbuf +; SJLJ: call invoke_vii +; SJLJ-NOT: call "__invoke_void_%struct.__jmp_buf_tag*_i32" ; SJLJ: call testSetjmp + ; NONE: call setjmp ; NONE: call longjmp } -declare void @foo() +declare void @foo(i32) declare i32 @__gxx_personality_v0(...) declare i8* @__cxa_begin_catch(i8*) declare void @__cxa_end_catch() @@ -59,3 +65,17 @@ attributes #0 = { returns_twice } attributes #1 = { noreturn } attributes #2 = { nounwind } + +; EH: .functype invoke_vi (i32, i32) -> () +; EH: .import_module invoke_vi, env +; EH: .import_name invoke_vi, invoke_vi +; EH-NOT: .functype __invoke_void_i32 +; EH-NOT: .import_module __invoke_void_i32 +; EH-NOT: .import_name __invoke_void_i32 + +; SJLJ: .functype emscripten_longjmp (i32, i32) -> () +; SJLJ: .import_module emscripten_longjmp, env +; SJLJ: .import_name emscripten_longjmp, emscripten_longjmp +; SJLJ-NOT: .functype emscripten_longjmp_jmpbuf +; SJLJ-NOT: .import_module emscripten_longjmp_jmpbuf +; SJLJ-NOT: .import_name emscripten_longjmp_jmpbuf diff --git a/llvm/test/CodeGen/WebAssembly/lower-em-sjlj-sret.ll b/llvm/test/CodeGen/WebAssembly/lower-em-sjlj-sret.ll --- a/llvm/test/CodeGen/WebAssembly/lower-em-sjlj-sret.ll +++ b/llvm/test/CodeGen/WebAssembly/lower-em-sjlj-sret.ll @@ -19,7 +19,7 @@ ; It needs to be the first argument (that's what we're testing here) ; CHECK: i32.const $push[[FPTR:[0-9]+]]=, returns_struct ; This is the sret stack region (as an offset from the stack pointer local) - ; CHECK: call "__invoke_{i32.i32}", $pop[[FPTR]] + ; CHECK: call invoke_vi, $pop[[FPTR]] %ret = call {i32, i32} @returns_struct() ret {i32, i32} %ret }