[llvm] 28780e5 - [WebAssembly] Add Wasm SjLj support
Heejin Ahn via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 2 10:51:35 PDT 2021
Author: Heejin Ahn
Date: 2021-09-02T10:51:02-07:00
New Revision: 28780e59f61537c4315581475697e96d94d73d48
URL: https://github.com/llvm/llvm-project/commit/28780e59f61537c4315581475697e96d94d73d48
DIFF: https://github.com/llvm/llvm-project/commit/28780e59f61537c4315581475697e96d94d73d48.diff
LOG: [WebAssembly] Add Wasm SjLj support
This add support for SjLj using Wasm exception handling instructions:
https://github.com/WebAssembly/exception-handling/blob/master/proposals/exception-handling/Exceptions.md
This does not yet support the mixed use of EH and SjLj within a
function. It will be added in a follow-up CL.
This currently passes all SjLj Emscripten tests for wasm0/1/2/3/s,
except for the below:
- `test_longjmp_standalone`: Uses Node
- `test_dlfcn_longjmp`: Uses NodeRAWFS
- `test_longjmp_throw`: Mixes EH and SjLj
- `test_exceptions_longjmp1`: Mixes EH and SjLj
- `test_exceptions_longjmp2`: Mixes EH and SjLj
- `test_exceptions_longjmp3`: Mixes EH and SjLj
Reviewed By: dschuff, tlively
Differential Revision: https://reviews.llvm.org/D108960
Added:
llvm/test/CodeGen/WebAssembly/lower-wasm-sjlj.ll
Modified:
llvm/lib/CodeGen/AsmPrinter/WasmException.cpp
llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
llvm/lib/MC/WasmObjectWriter.cpp
llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
llvm/test/CodeGen/WebAssembly/lower-em-sjlj.ll
Removed:
################################################################################
diff --git a/llvm/lib/CodeGen/AsmPrinter/WasmException.cpp b/llvm/lib/CodeGen/AsmPrinter/WasmException.cpp
index 352a33e8639d1..a833afd5c1d00 100644
--- a/llvm/lib/CodeGen/AsmPrinter/WasmException.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/WasmException.cpp
@@ -18,16 +18,18 @@
using namespace llvm;
void WasmException::endModule() {
- // This is the symbol used in 'throw' and 'catch' instruction to denote this
- // is a C++ exception. This symbol has to be emitted somewhere once in the
- // module. Check if the symbol has already been created, i.e., we have at
- // least one 'throw' or 'catch' instruction in the module, and emit the symbol
- // only if so.
- SmallString<60> NameStr;
- Mangler::getNameWithPrefix(NameStr, "__cpp_exception", Asm->getDataLayout());
- if (Asm->OutContext.lookupSymbol(NameStr)) {
- MCSymbol *ExceptionSym = Asm->GetExternalSymbolSymbol("__cpp_exception");
- Asm->OutStreamer->emitLabel(ExceptionSym);
+ // These are symbols used to throw/catch C++ exceptions and C longjmps. These
+ // symbols have to be emitted somewhere once in the module. Check if each of
+ // the symbols has already been created, i.e., we have at least one 'throw' or
+ // 'catch' instruction with the symbol in the module, and emit the symbol only
+ // if so.
+ for (const char *SymName : {"__cpp_exception", "__c_longjmp"}) {
+ SmallString<60> NameStr;
+ Mangler::getNameWithPrefix(NameStr, SymName, Asm->getDataLayout());
+ if (Asm->OutContext.lookupSymbol(NameStr)) {
+ MCSymbol *ExceptionSym = Asm->GetExternalSymbolSymbol(SymName);
+ Asm->OutStreamer->emitLabel(ExceptionSym);
+ }
}
}
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
index 3bd5db4e09b03..61e17f8bdca4c 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
@@ -1226,7 +1226,10 @@ static void mapWasmLandingPadIndex(MachineBasicBlock *MBB,
bool IsSingleCatchAllClause =
CPI->getNumArgOperands() == 1 &&
cast<Constant>(CPI->getArgOperand(0))->isNullValue();
- if (!IsSingleCatchAllClause) {
+ // cathchpads for longjmp use an empty type list, e.g. catchpad within %0 []
+ // and they don't need LSDA info
+ bool IsCatchLongjmp = CPI->getNumArgOperands() == 0;
+ if (!IsSingleCatchAllClause && !IsCatchLongjmp) {
// Create a mapping from landing pad label to landing pad index.
bool IntrFound = false;
for (const User *U : CPI->users()) {
diff --git a/llvm/lib/MC/WasmObjectWriter.cpp b/llvm/lib/MC/WasmObjectWriter.cpp
index 0dc5c9111db2c..7da5e1534bf15 100644
--- a/llvm/lib/MC/WasmObjectWriter.cpp
+++ b/llvm/lib/MC/WasmObjectWriter.cpp
@@ -1644,7 +1644,8 @@ uint64_t WasmObjectWriter::writeOneObject(MCAssembler &Asm,
LLVM_DEBUG(dbgs() << " -> table index: "
<< WasmIndices.find(&WS)->second << "\n");
} else if (WS.isTag()) {
- // C++ exception symbol (__cpp_exception)
+ // C++ exception symbol (__cpp_exception) or longjmp symbol
+ // (__c_longjmp)
unsigned Index;
if (WS.isDefined()) {
Index = NumTagImports + Tags.size();
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
index 395eb43a6fe23..17a9f3ea8b587 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
@@ -236,7 +236,7 @@ MCSymbol *WebAssemblyAsmPrinter::getOrCreateWasmSymbol(StringRef Name) {
SmallVector<wasm::ValType, 4> Returns;
SmallVector<wasm::ValType, 4> Params;
- if (Name == "__cpp_exception") {
+ if (Name == "__cpp_exception" || Name == "__c_longjmp") {
WasmSym->setType(wasm::WASM_SYMBOL_TYPE_TAG);
// We can't confirm its signature index for now because there can be
// imported exceptions. Set it to be 0 for now.
@@ -248,12 +248,14 @@ MCSymbol *WebAssemblyAsmPrinter::getOrCreateWasmSymbol(StringRef Name) {
WasmSym->setWeak(true);
WasmSym->setExternal(true);
- // All C++ exceptions are assumed to have a single i32 (for wasm32) or i64
- // (for wasm64) param type and void return type. The reaon is, all C++
- // exception values are pointers, and to share the type section with
- // functions, exceptions are assumed to have void return type.
- Params.push_back(Subtarget.hasAddr64() ? wasm::ValType::I64
- : wasm::ValType::I32);
+ // Currently both C++ exceptions and C longjmps have a single pointer type
+ // param. For C++ exceptions it is a pointer to an exception object, and for
+ // C longjmps it is pointer to a struct that contains a setjmp buffer and a
+ // longjmp return value. We may consider using multiple value parameters for
+ // longjmps later when multivalue support is ready.
+ wasm::ValType AddrType =
+ Subtarget.hasAddr64() ? wasm::ValType::I64 : wasm::ValType::I32;
+ Params.push_back(AddrType);
} else { // Function symbols
WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION);
getLibcallSignature(Subtarget, Name, Returns, Params);
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
index 4a7f28b04e565..d1d7c2d74a8bf 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
@@ -89,11 +89,13 @@ void WebAssemblyDAGToDAGISel::PreprocessISelDAG() {
}
static SDValue getTagSymNode(int Tag, SelectionDAG *DAG) {
- assert(Tag == WebAssembly::CPP_EXCEPTION);
+ assert(Tag == WebAssembly::CPP_EXCEPTION || WebAssembly::C_LONGJMP);
auto &MF = DAG->getMachineFunction();
const auto &TLI = DAG->getTargetLoweringInfo();
MVT PtrVT = TLI.getPointerTy(DAG->getDataLayout());
- const char *SymName = MF.createExternalSymbolName("__cpp_exception");
+ const char *SymName = Tag == WebAssembly::CPP_EXCEPTION
+ ? MF.createExternalSymbolName("__cpp_exception")
+ : MF.createExternalSymbolName("__c_longjmp");
return DAG->getTargetExternalSymbol(SymName, PtrVT);
}
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
index e9cad5e056781..d56c2c8bf8d46 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
@@ -7,15 +7,12 @@
//===----------------------------------------------------------------------===//
///
/// \file
-/// This file lowers exception-related instructions and setjmp/longjmp
-/// function calls in order to use Emscripten's JavaScript try and catch
-/// mechanism.
+/// This file lowers exception-related instructions and setjmp/longjmp function
+/// calls to use Emscripten's library functions. The pass uses JavaScript's try
+/// and catch mechanism in case of Emscripten EH/SjLj and Wasm EH intrinsics in
+/// case of Emscripten SjLJ.
///
-/// To handle exceptions and setjmp/longjmps, this scheme relies on JavaScript's
-/// try and catch syntax and relevant exception-related libraries implemented
-/// in JavaScript glue code that will be produced by Emscripten.
-///
-/// * Exception handling
+/// * Emscripten exception handling
/// This pass lowers invokes and landingpads into library functions in JS glue
/// code. Invokes are lowered into function wrappers called invoke wrappers that
/// exist in JS side, which wraps the original function call with JS try-catch.
@@ -23,7 +20,7 @@
/// variables (see below) so we can check whether an exception occurred from
/// wasm code and handle it appropriately.
///
-/// * Setjmp-longjmp handling
+/// * Emscripten setjmp-longjmp handling
/// This pass lowers setjmp to a reasonably-performant approach for emscripten.
/// The idea is that each block with a setjmp is broken up into two parts: the
/// part containing setjmp and the part right after the setjmp. The latter part
@@ -52,7 +49,7 @@
/// __threwValue is 0 for exceptions, and the argument to longjmp in case of
/// longjmp.
///
-/// * Exception handling
+/// * Emscripten exception handling
///
/// 2) We assume the existence of setThrew and setTempRet0/getTempRet0 functions
/// at link time. setThrew exists in Emscripten's compiler-rt:
@@ -121,16 +118,16 @@
/// call @llvm_eh_typeid_for(type)
/// llvm_eh_typeid_for function will be generated in JS glue code.
///
-/// * Setjmp / Longjmp handling
+/// * Emscripten setjmp / longjmp handling
///
-/// In case calls to longjmp() exists
+/// If there are calls to longjmp()
///
/// 1) Lower
/// longjmp(env, val)
/// into
/// emscripten_longjmp(env, val)
///
-/// In case calls to setjmp() exists
+/// If there are calls to setjmp()
///
/// 2) In the function entry that calls setjmp, initialize setjmpTable and
/// sejmpTableSize as follows:
@@ -154,7 +151,6 @@
/// the buffer 'env'. A BB with setjmp is split into two after setjmp call in
/// order to make the post-setjmp BB the possible destination of longjmp BB.
///
-///
/// 4) Lower every call that might longjmp into
/// __THREW__ = 0;
/// call @__invoke_SIG(func, arg1, arg2)
@@ -171,7 +167,7 @@
/// %label = -1;
/// }
/// longjmp_result = getTempRet0();
-/// switch label {
+/// switch %label {
/// label 1: goto post-setjmp BB 1
/// label 2: goto post-setjmp BB 2
/// ...
@@ -188,15 +184,98 @@
/// occurred. Otherwise we jump to the right post-setjmp BB based on the
/// label.
///
+/// * Wasm setjmp / longjmp handling
+/// This mode still uses some Emscripten library functions but not JavaScript's
+/// try-catch mechanism. It instead uses Wasm exception handling intrinsics,
+/// which will be lowered to exception handling instructions.
+///
+/// If there are calls to longjmp()
+///
+/// 1) Lower
+/// longjmp(env, val)
+/// into
+/// __wasm_longjmp(env, val)
+///
+/// If there are calls to setjmp()
+///
+/// 2) and 3): The same as 2) and 3) in Emscripten SjLj.
+/// (setjmpTable/setjmpTableSize initialization + setjmp callsite
+/// transformation)
+///
+/// 4) Create a catchpad with a wasm.catch() intrinsic, which returns the value
+/// thrown by __wasm_longjmp function. In Emscripten library, we have this
+/// struct:
+///
+/// struct __WasmLongjmpArgs {
+/// void *env;
+/// int val;
+/// };
+/// struct __WasmLongjmpArgs __wasm_longjmp_args;
+///
+/// The thrown value here is a pointer to __wasm_longjmp_args struct object. We
+/// use this struct to transfer two values by throwing a single value. Wasm
+/// throw and catch instructions are capable of throwing and catching multiple
+/// values, but it also requires multivalue support that is currently not very
+/// reliable.
+/// TODO Switch to throwing and catching two values without using the struct
+///
+/// All longjmpable function calls will be converted to an invoke that will
+/// unwind to this catchpad in case a longjmp occurs. Within the catchpad, we
+/// test the thrown values using testSetjmp function as we do for Emscripten
+/// SjLj. The main
diff erence is, in Emscripten SjLj, we need to transform every
+/// longjmpable callsite into a sequence of code including testSetjmp() call; in
+/// Wasm SjLj we do the testing in only one place, in this catchpad.
+///
+/// After testing calling testSetjmp(), if the longjmp does not correspond to
+/// one of the setjmps within the current function, it rethrows the longjmp
+/// by calling __wasm_longjmp(). If it corresponds to one of setjmps in the
+/// function, we jump to the beginning of the function, which contains a switch
+/// to each post-setjmp BB. Again, in Emscripten SjLj, this switch is added for
+/// every longjmpable callsite; in Wasm SjLj we do this only once at the top of
+/// the function. (after setjmpTable/setjmpTableSize initialization)
+///
+/// The below is the pseudocode for what we have described
+///
+/// entry:
+/// Initialize setjmpTable and setjmpTableSize
+///
+/// setjmp.dispatch:
+/// switch %label {
+/// label 1: goto post-setjmp BB 1
+/// label 2: goto post-setjmp BB 2
+/// ...
+/// default: goto splitted next BB
+/// }
+/// ...
+///
+/// bb:
+/// invoke void @foo() ;; foo is a longjmpable function
+/// to label %next unwind label %catch.dispatch.longjmp
+/// ...
+///
+/// catch.dispatch.longjmp:
+/// %0 = catchswitch within none [label %catch.longjmp] unwind to caller
+///
+/// catch.longjmp:
+/// %longjmp.args = wasm.catch() ;; struct __WasmLongjmpArgs
+/// %env = load 'env' field from __WasmLongjmpArgs
+/// %val = load 'val' field from __WasmLongjmpArgs
+/// %label = testSetjmp(mem[%env], setjmpTable, setjmpTableSize);
+/// if (%label == 0)
+/// __wasm_longjmp(%env, %val)
+/// catchret to %setjmp.dispatch
+///
///===----------------------------------------------------------------------===//
#include "WebAssembly.h"
#include "WebAssemblyTargetMachine.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/CodeGen/TargetPassConfig.h"
+#include "llvm/CodeGen/WasmEHFuncInfo.h"
#include "llvm/IR/DebugInfoMetadata.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/IntrinsicsWebAssembly.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/Transforms/Utils/SSAUpdater.h"
@@ -236,6 +315,11 @@ class WebAssemblyLowerEmscriptenEHSjLj final : public ModulePass {
Function *EmLongjmpF = nullptr; // emscripten_longjmp() (Emscripten)
Function *SaveSetjmpF = nullptr; // saveSetjmp() (Emscripten)
Function *TestSetjmpF = nullptr; // testSetjmp() (Emscripten)
+ Function *WasmLongjmpF = nullptr; // __wasm_longjmp() (Emscripten)
+ Function *CatchF = nullptr; // wasm.catch() (intrinsic)
+
+ // type of 'struct __WasmLongjmpArgs' defined in emscripten
+ Type *LongjmpArgsTy = nullptr;
// __cxa_find_matching_catch_N functions.
// Indexed by the number of clauses in an original landingpad instruction.
@@ -258,6 +342,10 @@ class WebAssemblyLowerEmscriptenEHSjLj final : public ModulePass {
Function &F, InstVector &SetjmpTableInsts,
InstVector &SetjmpTableSizeInsts,
SmallVectorImpl<PHINode *> &SetjmpRetPHIs);
+ void
+ handleLongjmpableCallsForWasmSjLj(Function &F, InstVector &SetjmpTableInsts,
+ InstVector &SetjmpTableSizeInsts,
+ SmallVectorImpl<PHINode *> &SetjmpRetPHIs);
Function *getFindMatchingCatch(Module &M, unsigned NumClauses);
Value *wrapInvoke(CallBase *CI);
@@ -274,6 +362,7 @@ class WebAssemblyLowerEmscriptenEHSjLj final : public ModulePass {
return EnableEmEH && (areAllExceptionsAllowed() ||
EHAllowlistSet.count(std::string(F->getName())));
}
+ void replaceLongjmpWith(Function *LongjmpF, Function *NewF);
void rebuildSSA(Function &F);
@@ -654,11 +743,17 @@ void WebAssemblyLowerEmscriptenEHSjLj::wrapTestSetjmp(
void WebAssemblyLowerEmscriptenEHSjLj::rebuildSSA(Function &F) {
DominatorTree &DT = getAnalysis<DominatorTreeWrapperPass>(F).getDomTree();
DT.recalculate(F); // CFG has been changed
+
SSAUpdaterBulk SSA;
for (BasicBlock &BB : F) {
for (Instruction &I : BB) {
unsigned VarID = SSA.AddVariable(I.getName(), I.getType());
- SSA.AddAvailableValue(VarID, &BB, &I);
+ // If a value is defined by an invoke instruction, it is only available in
+ // its normal destination and not in its unwind destination.
+ if (auto *II = dyn_cast<InvokeInst>(&I))
+ SSA.AddAvailableValue(VarID, II->getNormalDest(), II);
+ else
+ SSA.AddAvailableValue(VarID, &BB, &I);
for (auto &U : I.uses()) {
auto *User = cast<Instruction>(U.getUser());
if (auto *UserPN = dyn_cast<PHINode>(User))
@@ -673,26 +768,36 @@ void WebAssemblyLowerEmscriptenEHSjLj::rebuildSSA(Function &F) {
SSA.RewriteAllUses(&DT);
}
-// Replace uses of longjmp with emscripten_longjmp. emscripten_longjmp takes
-// arguments of type {i32, i32} (wasm32) / {i64, i32} (wasm64) and longjmp takes
-// {jmp_buf*, i32}, so we need a ptrtoint instruction here to make the type
-// match. jmp_buf* will eventually be lowered to i32/i64 in the wasm backend.
-static void replaceLongjmpWithEmscriptenLongjmp(Function *LongjmpF,
- Function *EmLongjmpF) {
+// Replace uses of longjmp with a new longjmp function in Emscripten library.
+// In Emscripten SjLj, the new function is
+// void emscripten_longjmp(uintptr_t, i32)
+// In Wasm SjLj, the new function is
+// void __wasm_longjmp(i8*, i32)
+// Because the original libc longjmp function takes (jmp_buf*, i32), we need a
+// ptrtoint/bitcast instruction here to make the type match. jmp_buf* will
+// eventually be lowered to i32/i64 in the wasm backend.
+void WebAssemblyLowerEmscriptenEHSjLj::replaceLongjmpWith(Function *LongjmpF,
+ Function *NewF) {
+ assert(NewF == EmLongjmpF || NewF == WasmLongjmpF);
Module *M = LongjmpF->getParent();
SmallVector<CallInst *, 8> ToErase;
LLVMContext &C = LongjmpF->getParent()->getContext();
IRBuilder<> IRB(C);
- // For calls to longjmp, replace it with emscripten_longjmp and cast its first
- // argument (jmp_buf*) to int
+ // For calls to longjmp, replace it with emscripten_longjmp/__wasm_longjmp and
+ // cast its first argument (jmp_buf*) appropriately
for (User *U : LongjmpF->users()) {
auto *CI = dyn_cast<CallInst>(U);
if (CI && CI->getCalledFunction() == LongjmpF) {
IRB.SetInsertPoint(CI);
- Value *Env =
- IRB.CreatePtrToInt(CI->getArgOperand(0), getAddrIntType(M), "env");
- IRB.CreateCall(EmLongjmpF, {Env, CI->getArgOperand(1)});
+ Value *Env = nullptr;
+ if (NewF == EmLongjmpF)
+ Env =
+ IRB.CreatePtrToInt(CI->getArgOperand(0), getAddrIntType(M), "env");
+ else // WasmLongjmpF
+ Env =
+ IRB.CreateBitCast(CI->getArgOperand(0), IRB.getInt8PtrTy(), "env");
+ IRB.CreateCall(NewF, {Env, CI->getArgOperand(1)});
ToErase.push_back(CI);
}
}
@@ -700,11 +805,11 @@ static void replaceLongjmpWithEmscriptenLongjmp(Function *LongjmpF,
I->eraseFromParent();
// If we have any remaining uses of longjmp's function pointer, replace it
- // with (int(*)(jmp_buf*, int))emscripten_longjmp.
+ // with (void(*)(jmp_buf*, int))emscripten_longjmp / __wasm_longjmp.
if (!LongjmpF->uses().empty()) {
- Value *EmLongjmp =
- IRB.CreateBitCast(EmLongjmpF, LongjmpF->getType(), "em_longjmp");
- LongjmpF->replaceAllUsesWith(EmLongjmp);
+ Value *NewLongjmp =
+ IRB.CreateBitCast(NewF, LongjmpF->getType(), "longjmp.cast");
+ LongjmpF->replaceAllUsesWith(NewLongjmp);
}
}
@@ -759,38 +864,49 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runOnModule(Module &M) {
EHTypeIDF = getEmscriptenFunction(EHTypeIDTy, "llvm_eh_typeid_for", &M);
}
- if (EnableEmSjLj && SetjmpF) {
+ if ((EnableEmSjLj || EnableWasmSjLj) && SetjmpF) {
// Precompute setjmp users
for (User *U : SetjmpF->users()) {
- Function *UserF = cast<Instruction>(U)->getFunction();
- // If a function that calls setjmp does not contain any other calls that
- // can longjmp, we don't need to do any transformation on that function,
- // so can ignore it
- if (containsLongjmpableCalls(UserF))
- SetjmpUsers.insert(UserF);
+ if (auto *UI = dyn_cast<Instruction>(U)) {
+ auto *UserF = UI->getFunction();
+ // If a function that calls setjmp does not contain any other calls that
+ // can longjmp, we don't need to do any transformation on that function,
+ // so can ignore it
+ if (containsLongjmpableCalls(UserF))
+ SetjmpUsers.insert(UserF);
+ }
}
}
bool SetjmpUsed = SetjmpF && !SetjmpUsers.empty();
bool LongjmpUsed = LongjmpF && !LongjmpF->use_empty();
- DoSjLj = EnableEmSjLj && (SetjmpUsed || LongjmpUsed);
+ DoSjLj = (EnableEmSjLj | EnableWasmSjLj) && (SetjmpUsed || LongjmpUsed);
// Function registration and data pre-gathering for setjmp/longjmp handling
if (DoSjLj) {
assert(EnableEmSjLj || EnableWasmSjLj);
- // Register emscripten_longjmp function
- FunctionType *FTy = FunctionType::get(
- IRB.getVoidTy(), {getAddrIntType(&M), IRB.getInt32Ty()}, false);
- EmLongjmpF = getEmscriptenFunction(FTy, "emscripten_longjmp", &M);
- EmLongjmpF->addFnAttr(Attribute::NoReturn);
+ if (EnableEmSjLj) {
+ // Register emscripten_longjmp function
+ FunctionType *FTy = FunctionType::get(
+ IRB.getVoidTy(), {getAddrIntType(&M), IRB.getInt32Ty()}, false);
+ EmLongjmpF = getEmscriptenFunction(FTy, "emscripten_longjmp", &M);
+ EmLongjmpF->addFnAttr(Attribute::NoReturn);
+ } else { // EnableWasmSjLj
+ // Register __wasm_longjmp function, which calls __builtin_wasm_longjmp.
+ FunctionType *FTy = FunctionType::get(
+ IRB.getVoidTy(), {IRB.getInt8PtrTy(), IRB.getInt32Ty()}, false);
+ WasmLongjmpF = getEmscriptenFunction(FTy, "__wasm_longjmp", &M);
+ WasmLongjmpF->addFnAttr(Attribute::NoReturn);
+ }
if (SetjmpF) {
// Register saveSetjmp function
FunctionType *SetjmpFTy = SetjmpF->getFunctionType();
- FTy = FunctionType::get(Type::getInt32PtrTy(C),
- {SetjmpFTy->getParamType(0), IRB.getInt32Ty(),
- Type::getInt32PtrTy(C), IRB.getInt32Ty()},
- false);
+ FunctionType *FTy =
+ FunctionType::get(Type::getInt32PtrTy(C),
+ {SetjmpFTy->getParamType(0), IRB.getInt32Ty(),
+ Type::getInt32PtrTy(C), IRB.getInt32Ty()},
+ false);
SaveSetjmpF = getEmscriptenFunction(FTy, "saveSetjmp", &M);
// Register testSetjmp function
@@ -799,6 +915,14 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runOnModule(Module &M) {
{getAddrIntType(&M), Type::getInt32PtrTy(C), IRB.getInt32Ty()},
false);
TestSetjmpF = getEmscriptenFunction(FTy, "testSetjmp", &M);
+
+ // wasm.catch() will be lowered down to wasm 'catch' instruction in
+ // instruction selection.
+ CatchF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_catch);
+ // Type for struct __WasmLongjmpArgs
+ LongjmpArgsTy = StructType::get(IRB.getInt8PtrTy(), // env
+ IRB.getInt32Ty() // val
+ );
}
}
@@ -815,7 +939,7 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runOnModule(Module &M) {
if (DoSjLj) {
Changed = true; // We have setjmp or longjmp somewhere
if (LongjmpF)
- replaceLongjmpWithEmscriptenLongjmp(LongjmpF, EmLongjmpF);
+ replaceLongjmpWith(LongjmpF, EnableEmSjLj ? EmLongjmpF : WasmLongjmpF);
// Only traverse functions that uses setjmp in order not to insert
// unnecessary prep / cleanup code in every function
if (SetjmpF)
@@ -890,7 +1014,8 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runEHOnFunction(Function &F) {
//
// tail: ;; Nothing happened or an exception is thrown
// ... Continue exception handling ...
- if (DoSjLj && !SetjmpUsers.count(&F) && canLongjmp(Callee)) {
+ if (DoSjLj && EnableEmSjLj && !SetjmpUsers.count(&F) &&
+ canLongjmp(Callee)) {
// Create longjmp.rethrow BB once and share it within the function
if (!RethrowLongjmpBB) {
RethrowLongjmpBB = BasicBlock::Create(C, "rethrow.longjmp", &F);
@@ -1130,9 +1255,13 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runSjLjOnFunction(Function &F) {
ToErase.push_back(CI);
}
- // Handle longjmp calls.
- handleLongjmpableCallsForEmscriptenSjLj(F, SetjmpTableInsts,
- SetjmpTableSizeInsts, SetjmpRetPHIs);
+ // Handle longjmpable calls.
+ if (EnableEmSjLj)
+ handleLongjmpableCallsForEmscriptenSjLj(
+ F, SetjmpTableInsts, SetjmpTableSizeInsts, SetjmpRetPHIs);
+ else // EnableWasmSjLj
+ handleLongjmpableCallsForWasmSjLj(F, SetjmpTableInsts, SetjmpTableSizeInsts,
+ SetjmpRetPHIs);
// Erase everything we no longer need in this function
for (Instruction *I : ToErase)
@@ -1152,7 +1281,7 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runSjLjOnFunction(Function &F) {
for (auto &I : BB) {
if (auto *CI = dyn_cast<CallInst>(&I)) {
bool IsNoReturn = CI->hasFnAttr(Attribute::NoReturn);
- if (auto *CalleeF = dyn_cast<Function>(CI->getCalledOperand()))
+ if (Function *CalleeF = CI->getCalledFunction())
IsNoReturn |= CalleeF->hasFnAttribute(Attribute::NoReturn);
if (IsNoReturn)
ExitingInsts.push_back(&I);
@@ -1161,7 +1290,13 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runSjLjOnFunction(Function &F) {
}
for (auto *I : ExitingInsts) {
DebugLoc DL = getOrCreateDebugLoc(I, F.getSubprogram());
- auto *Free = CallInst::CreateFree(SetjmpTable, I);
+ // If this existing instruction is a call within a catchpad, we should add
+ // it as "funclet" to the operand bundle of 'free' call
+ SmallVector<OperandBundleDef, 1> Bundles;
+ if (auto *CB = dyn_cast<CallBase>(I))
+ if (auto Bundle = CB->getOperandBundle(LLVMContext::OB_funclet))
+ Bundles.push_back(OperandBundleDef(*Bundle));
+ auto *Free = CallInst::CreateFree(SetjmpTable, Bundles, I);
Free->setDebugLoc(DL);
// CallInst::CreateFree may create a bitcast instruction if its argument
// types mismatch. We need to set the debug loc for the bitcast too.
@@ -1215,8 +1350,9 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runSjLjOnFunction(Function &F) {
return true;
}
-// Update each call that can longjmp so it can return to a setjmp where
-// relevant.
+// Update each call that can longjmp so it can return to the corresponding
+// setjmp. Refer to 4) of "Emscripten setjmp/longjmp handling" section in the
+// comments at top of the file for details.
void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForEmscriptenSjLj(
Function &F, InstVector &SetjmpTableInsts, InstVector &SetjmpTableSizeInsts,
SmallVectorImpl<PHINode *> &SetjmpRetPHIs) {
@@ -1402,3 +1538,181 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForEmscriptenSjLj(
for (Instruction *I : ToErase)
I->eraseFromParent();
}
+
+// Create a catchpad in which we catch a longjmp's env and val arguments, test
+// if the longjmp corresponds to one of setjmps in the current function, and if
+// so, jump to the setjmp dispatch BB from which we go to one of post-setjmp
+// BBs. Refer to 4) of "Wasm setjmp/longjmp handling" section in the comments at
+// top of the file for details.
+void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(
+ Function &F, InstVector &SetjmpTableInsts, InstVector &SetjmpTableSizeInsts,
+ SmallVectorImpl<PHINode *> &SetjmpRetPHIs) {
+ Module &M = *F.getParent();
+ LLVMContext &C = F.getContext();
+ IRBuilder<> IRB(C);
+
+ // A function with catchswitch/catchpad instruction should have a personality
+ // function attached to it. Search for the wasm personality function, and if
+ // it exists, use it, and if it doesn't, create a dummy personality function.
+ // (SjLj is not going to call it anyway.)
+ if (!F.hasPersonalityFn()) {
+ StringRef PersName = getEHPersonalityName(EHPersonality::Wasm_CXX);
+ FunctionType *PersType =
+ FunctionType::get(IRB.getInt32Ty(), /* isVarArg */ true);
+ Value *PersF = M.getOrInsertFunction(PersName, PersType).getCallee();
+ F.setPersonalityFn(
+ cast<Constant>(IRB.CreateBitCast(PersF, IRB.getInt8PtrTy())));
+ }
+
+ // Use the entry BB's debugloc as a fallback
+ BasicBlock *Entry = &F.getEntryBlock();
+ DebugLoc FirstDL = getOrCreateDebugLoc(&*Entry->begin(), F.getSubprogram());
+ IRB.SetCurrentDebugLocation(FirstDL);
+
+ // Arbitrarily use the ones defined in the beginning of the function.
+ // SSAUpdater will later update them to the correct values.
+ Instruction *SetjmpTable = *SetjmpTableInsts.begin();
+ Instruction *SetjmpTableSize = *SetjmpTableSizeInsts.begin();
+
+ // Add setjmp.dispatch BB right after the entry block. Because we have
+ // initialized setjmpTable/setjmpTableSize in the entry block and split the
+ // rest into another BB, here 'OrigEntry' is the function's original entry
+ // block before the transformation.
+ //
+ // entry:
+ // setjmpTable / setjmpTableSize initialization
+ // setjmp.dispatch:
+ // switch will be inserted here later
+ // entry.split: (OrigEntry)
+ // the original function starts here
+ BasicBlock *OrigEntry = Entry->getNextNode();
+ BasicBlock *SetjmpDispatchBB =
+ BasicBlock::Create(C, "setjmp.dispatch", &F, OrigEntry);
+ cast<BranchInst>(Entry->getTerminator())->setSuccessor(0, SetjmpDispatchBB);
+
+ // Create catch.dispatch.longjmp BB a catchswitch instruction
+ BasicBlock *CatchSwitchBB =
+ BasicBlock::Create(C, "catch.dispatch.longjmp", &F);
+ IRB.SetInsertPoint(CatchSwitchBB);
+ CatchSwitchInst *CatchSwitch =
+ IRB.CreateCatchSwitch(ConstantTokenNone::get(C), nullptr, 1);
+
+ // Create catch.longjmp BB and a catchpad instruction
+ BasicBlock *CatchLongjmpBB = BasicBlock::Create(C, "catch.longjmp", &F);
+ CatchSwitch->addHandler(CatchLongjmpBB);
+ IRB.SetInsertPoint(CatchLongjmpBB);
+ CatchPadInst *CatchPad = IRB.CreateCatchPad(CatchSwitch, {});
+
+ // Wasm throw and catch instructions can throw and catch multiple values, but
+ // that requires multivalue support in the toolchain, which is currently not
+ // very reliable. We instead throw and catch a pointer to a struct value of
+ // type 'struct __WasmLongjmpArgs', which is defined in Emscripten.
+ Instruction *CatchCI =
+ IRB.CreateCall(CatchF, {IRB.getInt32(WebAssembly::C_LONGJMP)}, "thrown");
+ Value *LongjmpArgs =
+ IRB.CreateBitCast(CatchCI, LongjmpArgsTy->getPointerTo(), "longjmp.args");
+ Value *EnvField =
+ IRB.CreateConstGEP2_32(LongjmpArgsTy, LongjmpArgs, 0, 0, "env_gep");
+ Value *ValField =
+ IRB.CreateConstGEP2_32(LongjmpArgsTy, LongjmpArgs, 0, 1, "val_gep");
+ // void *env = __wasm_longjmp_args.env;
+ Instruction *Env = IRB.CreateLoad(IRB.getInt8PtrTy(), EnvField, "env");
+ // int val = __wasm_longjmp_args.val;
+ Instruction *Val = IRB.CreateLoad(IRB.getInt32Ty(), ValField, "val");
+
+ // %label = testSetjmp(mem[%env], setjmpTable, setjmpTableSize);
+ // if (%label == 0)
+ // __wasm_longjmp(%env, %val)
+ // catchret to %setjmp.dispatch
+ BasicBlock *ThenBB = BasicBlock::Create(C, "if.then", &F);
+ BasicBlock *EndBB = BasicBlock::Create(C, "if.end", &F);
+ Value *EnvP = IRB.CreateBitCast(Env, getAddrPtrType(&M), "env.p");
+ Value *SetjmpID = IRB.CreateLoad(getAddrIntType(&M), EnvP, "setjmp.id");
+ Value *Label =
+ IRB.CreateCall(TestSetjmpF, {SetjmpID, SetjmpTable, SetjmpTableSize},
+ OperandBundleDef("funclet", CatchPad), "label");
+ Value *Cmp = IRB.CreateICmpEQ(Label, IRB.getInt32(0));
+ IRB.CreateCondBr(Cmp, ThenBB, EndBB);
+
+ IRB.SetInsertPoint(ThenBB);
+ CallInst *WasmLongjmpCI = IRB.CreateCall(
+ WasmLongjmpF, {Env, Val}, OperandBundleDef("funclet", CatchPad));
+ IRB.CreateUnreachable();
+
+ IRB.SetInsertPoint(EndBB);
+ // Jump to setjmp.dispatch block
+ IRB.CreateCatchRet(CatchPad, SetjmpDispatchBB);
+
+ // Go back to setjmp.dispatch BB
+ // setjmp.dispatch:
+ // switch %label {
+ // label 1: goto post-setjmp BB 1
+ // label 2: goto post-setjmp BB 2
+ // ...
+ // default: goto splitted next BB
+ // }
+ IRB.SetInsertPoint(SetjmpDispatchBB);
+ PHINode *LabelPHI = IRB.CreatePHI(IRB.getInt32Ty(), 2, "label.phi");
+ LabelPHI->addIncoming(Label, EndBB);
+ LabelPHI->addIncoming(IRB.getInt32(-1), Entry);
+ SwitchInst *SI = IRB.CreateSwitch(LabelPHI, OrigEntry, SetjmpRetPHIs.size());
+ // -1 means no longjmp happened, continue normally (will hit the default
+ // switch case). 0 means a longjmp that is not ours to handle, needs a
+ // rethrow. Otherwise the index is the same as the index in P+1 (to avoid
+ // 0).
+ for (unsigned I = 0; I < SetjmpRetPHIs.size(); I++) {
+ SI->addCase(IRB.getInt32(I + 1), SetjmpRetPHIs[I]->getParent());
+ SetjmpRetPHIs[I]->addIncoming(Val, SetjmpDispatchBB);
+ }
+
+ // Convert all longjmpable call instructions to invokes that unwind to the
+ // newly created catch.dispatch.longjmp BB.
+ SmallVector<Instruction *, 64> ToErase;
+ for (auto *BB = &*F.begin(); BB; BB = BB->getNextNode()) {
+ for (Instruction &I : *BB) {
+ auto *CI = dyn_cast<CallInst>(&I);
+ if (!CI)
+ continue;
+ const Value *Callee = CI->getCalledOperand();
+ if (!canLongjmp(Callee))
+ continue;
+ if (isEmAsmCall(Callee))
+ report_fatal_error("Cannot use EM_ASM* alongside setjmp/longjmp in " +
+ F.getName() +
+ ". Please consider using EM_JS, or move the "
+ "EM_ASM into another function.",
+ false);
+ // This is __wasm_longjmp() call we inserted in this function, which
+ // rethrows the longjmp when the longjmp does not correspond to one of
+ // setjmps in this function. We should not convert this call to an invoke.
+ if (CI == WasmLongjmpCI)
+ continue;
+ ToErase.push_back(CI);
+
+ // Even if the callee function has attribute 'nounwind', which is true for
+ // all C functions, it can longjmp, which means it can throw a Wasm
+ // exception now.
+ CI->removeFnAttr(Attribute::NoUnwind);
+ if (Function *CalleeF = CI->getCalledFunction()) {
+ CalleeF->removeFnAttr(Attribute::NoUnwind);
+ }
+
+ IRB.SetInsertPoint(CI);
+ BasicBlock *Tail = SplitBlock(BB, CI->getNextNode());
+ // We will add a new invoke. So remove the branch created when we split
+ // the BB
+ ToErase.push_back(BB->getTerminator());
+ SmallVector<Value *, 8> Args(CI->args());
+ InvokeInst *II =
+ IRB.CreateInvoke(CI->getFunctionType(), CI->getCalledOperand(), Tail,
+ CatchSwitchBB, Args);
+ II->takeName(CI);
+ II->setDebugLoc(CI->getDebugLoc());
+ II->setAttributes(CI->getAttributes());
+ CI->replaceAllUsesWith(II);
+ }
+ }
+
+ for (Instruction *I : ToErase)
+ I->eraseFromParent();
+}
diff --git a/llvm/test/CodeGen/WebAssembly/lower-em-sjlj.ll b/llvm/test/CodeGen/WebAssembly/lower-em-sjlj.ll
index 5615901316b62..1186b58373f2b 100644
--- a/llvm/test/CodeGen/WebAssembly/lower-em-sjlj.ll
+++ b/llvm/test/CodeGen/WebAssembly/lower-em-sjlj.ll
@@ -254,7 +254,7 @@ for.inc: ; preds = %for.cond
; Tests cases where longjmp function pointer is used in other ways than direct
; calls. longjmps should be replaced with
-; (int(*)(jmp_buf*, int))emscripten_longjmp.
+; (void(*)(jmp_buf*, int))emscripten_longjmp.
declare void @take_longjmp(void (%struct.__jmp_buf_tag*, i32)* %arg_ptr)
define void @indirect_longjmp() {
; CHECK-LABEL: @indirect_longjmp
diff --git a/llvm/test/CodeGen/WebAssembly/lower-wasm-sjlj.ll b/llvm/test/CodeGen/WebAssembly/lower-wasm-sjlj.ll
new file mode 100644
index 0000000000000..cf66d7d7b4811
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/lower-wasm-sjlj.ll
@@ -0,0 +1,161 @@
+; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-sjlj -S | FileCheck %s -DPTR=i32
+; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-sjlj --mtriple=wasm64-unknown-unknown -data-layout="e-m:e-p:64:64-i64:64-n32:64-S128" -S | FileCheck %s -DPTR=i64
+
+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] }
+
+ at global_longjmp_ptr = global void (%struct.__jmp_buf_tag*, i32)* @longjmp, align 4
+; CHECK-DAG: @global_longjmp_ptr = global void (%struct.__jmp_buf_tag*, i32)* bitcast (void (i8*, i32)* @__wasm_longjmp to void (%struct.__jmp_buf_tag*, i32)*)
+
+; Test a simple setjmp - longjmp sequence
+define void @setjmp_longjmp() {
+; CHECK-LABEL: @setjmp_longjmp() personality {{.*}} @__gxx_wasm_personality_v0
+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 void @longjmp(%struct.__jmp_buf_tag* %arraydecay1, i32 1) #1
+ unreachable
+
+; CHECK: entry:
+; CHECK-NEXT: %malloccall = tail call i8* @malloc(i32 40)
+; CHECK-NEXT: %setjmpTable = bitcast i8* %malloccall to i32*
+; CHECK-NEXT: store i32 0, i32* %setjmpTable, align 4
+; CHECK-NEXT: %setjmpTableSize = add i32 4, 0
+; CHECK-NEXT: br label %setjmp.dispatch
+
+; CHECK: setjmp.dispatch:
+; CHECK-NEXT: %val10 = phi i32 [ %val, %if.end ], [ undef, %entry ]
+; CHECK-NEXT: %buf9 = phi [1 x %struct.__jmp_buf_tag]* [ %buf8, %if.end ], [ undef, %entry ]
+; CHECK-NEXT: %setjmpTableSize6 = phi i32 [ %setjmpTableSize7, %if.end ], [ %setjmpTableSize, %entry ]
+; CHECK-NEXT: %setjmpTable4 = phi i32* [ %setjmpTable5, %if.end ], [ %setjmpTable, %entry ]
+; CHECK-NEXT: %label.phi = phi i32 [ %label, %if.end ], [ -1, %entry ]
+; CHECK-NEXT: switch i32 %label.phi, label %entry.split [
+; CHECK-NEXT: i32 1, label %entry.split.split
+; CHECK-NEXT: ]
+
+; CHECK: entry.split:
+; CHECK-NEXT: %buf = alloca [1 x %struct.__jmp_buf_tag], align 16
+; CHECK-NEXT: %arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf, i32 0, i32 0
+; CHECK-NEXT: %setjmpTable1 = call i32* @saveSetjmp(%struct.__jmp_buf_tag* %arraydecay, i32 1, i32* %setjmpTable4, i32 %setjmpTableSize6)
+; CHECK-NEXT: %setjmpTableSize2 = call i32 @getTempRet0()
+; CHECK-NEXT: br label %entry.split.split
+
+; CHECK: entry.split.split:
+; CHECK-NEXT: %buf8 = phi [1 x %struct.__jmp_buf_tag]* [ %buf9, %setjmp.dispatch ], [ %buf, %entry.split ]
+; CHECK-NEXT: %setjmpTableSize7 = phi i32 [ %setjmpTableSize2, %entry.split ], [ %setjmpTableSize6, %setjmp.dispatch ]
+; CHECK-NEXT: %setjmpTable5 = phi i32* [ %setjmpTable1, %entry.split ], [ %setjmpTable4, %setjmp.dispatch ]
+; CHECK-NEXT: %setjmp.ret = phi i32 [ 0, %entry.split ], [ %val10, %setjmp.dispatch ]
+; CHECK-NEXT: %arraydecay1 = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf8, i32 0, i32 0
+; CHECK-NEXT: %env = bitcast %struct.__jmp_buf_tag* %arraydecay1 to i8*
+; CHECK-NEXT: invoke void @__wasm_longjmp(i8* %env, i32 1)
+; CHECK-NEXT: to label %entry.split.split.split unwind label %catch.dispatch.longjmp
+
+; CHECK: entry.split.split.split:
+; CHECK-NEXT: unreachable
+
+; CHECK: catch.dispatch.longjmp:
+; CHECK-NEXT: %0 = catchswitch within none [label %catch.longjmp] unwind to caller
+
+; CHECK: catch.longjmp:
+; CHECK-NEXT: %1 = catchpad within %0 []
+; CHECK-NEXT: %thrown = call i8* @llvm.wasm.catch(i32 1)
+; CHECK-NEXT: %longjmp.args = bitcast i8* %thrown to { i8*, i32 }*
+; CHECK-NEXT: %env_gep = getelementptr { i8*, i32 }, { i8*, i32 }* %longjmp.args, i32 0, i32 0
+; CHECK-NEXT: %val_gep = getelementptr { i8*, i32 }, { i8*, i32 }* %longjmp.args, i32 0, i32 1
+; CHECK-NEXT: %env3 = load i8*, i8** %env_gep, align {{.*}}
+; CHECK-NEXT: %val = load i32, i32* %val_gep, align 4
+; CHECK-NEXT: %env.p = bitcast i8* %env3 to [[PTR]]*
+; CHECK-NEXT: %setjmp.id = load [[PTR]], [[PTR]]* %env.p, align {{.*}}
+; CHECK-NEXT: %label = call i32 @testSetjmp([[PTR]] %setjmp.id, i32* %setjmpTable5, i32 %setjmpTableSize7) [ "funclet"(token %1) ]
+; CHECK-NEXT: %2 = icmp eq i32 %label, 0
+; CHECK-NEXT: br i1 %2, label %if.then, label %if.end
+
+; CHECK: if.then:
+; CHECK-NEXT: %3 = bitcast i32* %setjmpTable5 to i8*
+; CHECK-NEXT: tail call void @free(i8* %3) [ "funclet"(token %1) ]
+; CHECK-NEXT: call void @__wasm_longjmp(i8* %env3, i32 %val) [ "funclet"(token %1) ]
+; CHECK-NEXT: unreachable
+
+; CHECK: if.end:
+; CHECK-NEXT: catchret from %1 to label %setjmp.dispatch
+}
+
+; When there are multiple longjmpable calls after setjmp. This will turn each of
+; longjmpable call into an invoke whose unwind destination is
+; 'catch.dispatch.longjmp' BB.
+define void @setjmp_multiple_longjmpable_calls() {
+; CHECK-LABEL: @setjmp_multiple_longjmpable_calls
+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
+ call void @foo()
+ call void @foo()
+ ret void
+
+; CHECK: entry.split.split:
+; CHECK: invoke void @foo()
+; CHECK: to label %{{.*}} unwind label %catch.dispatch.longjmp
+
+; CHECK: entry.split.split.split:
+; CHECK: invoke void @foo()
+; CHECK: to label %{{.*}} unwind label %catch.dispatch.longjmp
+}
+
+; Tests cases where longjmp function pointer is used in other ways than direct
+; calls. longjmps should be replaced with (void(*)(jmp_buf*, int))__wasm_longjmp.
+declare void @take_longjmp(void (%struct.__jmp_buf_tag*, i32)* %arg_ptr)
+define void @indirect_longjmp() {
+; CHECK-LABEL: @indirect_longjmp
+entry:
+ %local_longjmp_ptr = alloca void (%struct.__jmp_buf_tag*, i32)*, align 4
+ %buf0 = alloca [1 x %struct.__jmp_buf_tag], align 16
+ %buf1 = alloca [1 x %struct.__jmp_buf_tag], align 16
+
+ ; Store longjmp in a local variable, load it, and call it
+ store void (%struct.__jmp_buf_tag*, i32)* @longjmp, void (%struct.__jmp_buf_tag*, i32)** %local_longjmp_ptr, align 4
+ ; CHECK: store void (%struct.__jmp_buf_tag*, i32)* bitcast (void (i8*, i32)* @__wasm_longjmp to void (%struct.__jmp_buf_tag*, i32)*), void (%struct.__jmp_buf_tag*, i32)** %local_longjmp_ptr, align 4
+ %longjmp_from_local_ptr = load void (%struct.__jmp_buf_tag*, i32)*, void (%struct.__jmp_buf_tag*, i32)** %local_longjmp_ptr, align 4
+ %arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf0, i32 0, i32 0
+ call void %longjmp_from_local_ptr(%struct.__jmp_buf_tag* %arraydecay, i32 0)
+
+ ; Load longjmp from a global variable and call it
+ %longjmp_from_global_ptr = load void (%struct.__jmp_buf_tag*, i32)*, void (%struct.__jmp_buf_tag*, i32)** @global_longjmp_ptr, align 4
+ %arraydecay1 = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf1, i32 0, i32 0
+ call void %longjmp_from_global_ptr(%struct.__jmp_buf_tag* %arraydecay1, i32 0)
+
+ ; Pass longjmp as a function argument. This is a call but longjmp is not a
+ ; callee but an argument.
+ call void @take_longjmp(void (%struct.__jmp_buf_tag*, i32)* @longjmp)
+ ; CHECK: call void @take_longjmp(void (%struct.__jmp_buf_tag*, i32)* bitcast (void (i8*, i32)* @__wasm_longjmp to void (%struct.__jmp_buf_tag*, i32)*))
+ ret void
+}
+
+; Function Attrs: nounwind
+declare void @foo() #2
+; The pass removes the 'nounwind' attribute, so there should be no attributes
+; CHECK-NOT: declare void @foo #{{.*}}
+; Function Attrs: returns_twice
+declare i32 @setjmp(%struct.__jmp_buf_tag*) #0
+; Function Attrs: noreturn
+declare void @longjmp(%struct.__jmp_buf_tag*, i32) #1
+declare i32 @__gxx_personality_v0(...)
+declare i8* @__cxa_begin_catch(i8*)
+declare void @__cxa_end_catch()
+declare i8* @malloc(i32)
+declare void @free(i8*)
+
+; JS glue function declarations
+; CHECK-DAG: declare i32 @getTempRet0()
+; CHECK-DAG: declare void @setTempRet0(i32)
+; CHECK-DAG: declare i32* @saveSetjmp(%struct.__jmp_buf_tag*, i32, i32*, i32)
+; CHECK-DAG: declare i32 @testSetjmp([[PTR]], i32*, i32)
+; CHECK-DAG: declare void @__wasm_longjmp(i8*, i32)
+
+attributes #0 = { returns_twice }
+attributes #1 = { noreturn }
+attributes #2 = { nounwind }
More information about the llvm-commits
mailing list