//===----------------------------------------------------------------------===//
///
/// \file
-/// \brief This file lowers exception-related instructions in order to use
-/// Emscripten's JavaScript try and catch mechanism to handle exceptions.
+/// \brief This file lowers exception-related instructions and setjmp/longjmp
+/// function calls in order to use Emscripten's JavaScript try and catch
+/// mechanism.
///
-/// To handle exceptions, 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. This is similar to the
-/// current Emscripten asm.js exception handling in fastcomp.
-/// For fastcomp's EH scheme, see these files in fastcomp LLVM branch:
+/// 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. This is similar
+/// to the current Emscripten asm.js exception handling in fastcomp. For
+/// fastcomp's EH / SjLj scheme, see these files in fastcomp LLVM branch:
/// (Location: https://github.com/kripken/emscripten-fastcomp)
/// lib/Target/JSBackend/NaCl/LowerEmExceptionsPass.cpp
+/// lib/Target/JSBackend/NaCl/LowerEmSetjmp.cpp
/// lib/Target/JSBackend/JSBackend.cpp
/// lib/Target/JSBackend/CallHandlers.h
///
-/// This pass does following things:
+/// * 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.
+/// If an exception occurred, cxa_throw() function in JS side sets some
+/// variables (see below) so we can check whether an exception occurred from
+/// wasm code and handle it appropriately.
///
-/// 1) Create three global variables: __THREW__, __threwValue, and tempRet0.
-/// tempRet0 will be set within __cxa_find_matching_catch() function in
+/// * 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
+/// is either reached from the setjmp, or later from a longjmp. To handle the
+/// longjmp, all calls that might longjmp are also called using invoke wrappers
+/// and thus JS / try-catch. JS longjmp() function also sets some variables so
+/// we can check / whether a longjmp occurred from wasm code. Each block with a
+/// function call that might longjmp is also split up after the longjmp call.
+/// After the longjmp call, we check whether a longjmp occurred, and if it did,
+/// which setjmp it corresponds to, and jump to the right post-setjmp block.
+/// We assume setjmp-longjmp handling always run after EH handling, which means
+/// we don't expect any exception-related instructions when SjLj runs.
+/// FIXME Currently this scheme does not support indirect call of setjmp,
+/// because of the limitation of the scheme itself. fastcomp does not support it
+/// either.
+///
+/// In detail, this pass does following things:
+///
+/// 1) Create three global variables: __THREW__, __threwValue, and __tempRet0.
+/// __tempRet0 will be set within __cxa_find_matching_catch() function in
/// JS library, and __THREW__ and __threwValue will be set in invoke wrappers
-/// in JS glue code. For what invoke wrappers are, refer to 3).
+/// in JS glue code. For what invoke wrappers are, refer to 3). These
+/// variables are used for both exceptions and setjmp/longjmps.
+/// __THREW__ indicates whether an exception or a longjmp occurred or not. 0
+/// means nothing occurred, 1 means an exception occurred, and other numbers
+/// mean a longjmp occurred. In the case of longjmp, threwValue variable
+/// indicates the corresponding setjmp buffer the longjmp corresponds to.
+/// In exception handling, __tempRet0 indicates the type of an exception
+/// caught, and in setjmp/longjmp, it means the second argument to longjmp
+/// function.
+///
+/// * Exception handling
///
/// 2) Create setThrew and setTempRet0 functions.
/// The global variables created in 1) will exist in wasm address space,
/// }
///
/// function setTempRet0(value) {
-/// tempRet0 = value;
+/// __tempRet0 = value;
/// }
///
/// 3) Lower
/// ... use %val ...
/// into
/// %fmc = call @__cxa_find_matching_catch_N(c1, c2, c3, ...)
-/// %val = {%fmc, tempRet0}
+/// %val = {%fmc, __tempRet0}
/// ... use %val ...
/// Here N is a number calculated based on the number of clauses.
-/// Global variable tempRet0 is set within __cxa_find_matching_catch() in
+/// Global variable __tempRet0 is set within __cxa_find_matching_catch() in
/// JS glue code.
///
/// 5) Lower
/// call @llvm_eh_typeid_for(type)
/// llvm_eh_typeid_for function will be generated in JS glue code.
///
+/// * Setjmp / Longjmp handling
+///
+/// 7) In the function entry that calls setjmp, initialize setjmpTable and
+/// sejmpTableSize as follows:
+/// setjmpTableSize = 4;
+/// setjmpTable = (int *) malloc(40);
+/// setjmpTable[0] = 0;
+/// setjmpTable and setjmpTableSize are used in saveSetjmp() function in JS
+/// code.
+///
+/// 8) Lower
+/// setjmp(buf)
+/// into
+/// setjmpTable = saveSetjmp(buf, label, setjmpTable, setjmpTableSize);
+/// setjmpTableSize = __tempRet0;
+/// For each dynamic setjmp call, setjmpTable stores its ID (a number which
+/// is incrementally assigned from 0) and its label (a unique number that
+/// represents each callsite of setjmp). When we need more entries in
+/// setjmpTable, it is reallocated in saveSetjmp() in JS code and it will
+/// return the new table address, and assign the new table size in
+/// __tempRet0. saveSetjmp also stores the setjmp's ID into the buffer buf.
+/// 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.
+///
+/// 9) Lower
+/// longjmp(buf, value)
+/// into
+/// emscripten_longjmp_jmpbuf(buf, value)
+/// emscripten_longjmp_jmpbuf will be lowered to emscripten_longjmp later.
+///
+/// 10) Lower every call that might longjmp into
+/// __THREW__ = 0;
+/// call @invoke_SIG(func, arg1, arg2)
+/// %__THREW__.val = __THREW__;
+/// __THREW__ = 0;
+/// if (%__THREW__.val != 0 & threwValue != 0) {
+/// %label = testSetjmp(mem[%__THREW__.val], setjmpTable,
+/// setjmpTableSize);
+/// if (%label == 0)
+/// emscripten_longjmp(%__THREW__.val, threwValue);
+/// __tempRet0 = threwValue;
+/// } else {
+/// %label = -1;
+/// }
+/// longjmp_result = __tempRet0;
+/// switch label {
+/// label 1: post-setjmp BB 1
+/// label 2: post-setjmp BB 2
+/// ...
+/// default: splited next BB
+/// }
+/// testSetjmp examines setjmpTable to see if there is a matching setjmp
+/// call. After calling an invoke wrapper, if a longjmp occurred, __THREW__
+/// will be the address of matching jmp_buf buffer and threwValue be the
+/// second argument to longjmp. mem[__THREW__.val] is a setjmp ID that is
+/// stored in saveSetjmp. testSetjmp returns a setjmp label, a unique ID to
+/// each setjmp callsite. Label 0 means this longjmp buffer does not
+/// correspond to one of the setjmp callsites in this function, so in this
+/// case we just chain the longjmp to the caller. (Here we call
+/// 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.)
+/// Label -1 means no longjmp occurred. Otherwise we jump to the right
+/// post-setjmp BB based on the label.
+///
///===----------------------------------------------------------------------===//
#include "WebAssembly.h"
#include "llvm/IR/CallSite.h"
+#include "llvm/IR/Dominators.h"
#include "llvm/IR/IRBuilder.h"
-#include "llvm/IR/Module.h"
-#include "llvm/Support/raw_ostream.h"
-#include <set>
+#include "llvm/Transforms/Utils/BasicBlockUtils.h"
+#include "llvm/Transforms/Utils/SSAUpdater.h"
using namespace llvm;
static const char *EHTypeIDFName;
static const char *SetThrewFName;
static const char *SetTempRet0FName;
+ static const char *EmLongjmpFName;
+ static const char *EmLongjmpJmpbufFName;
+ static const char *SaveSetjmpFName;
+ static const char *TestSetjmpFName;
static const char *FindMatchingCatchPrefix;
static const char *InvokePrefix;
- bool DoEH; // Enable exception handling
- bool DoSjLj; // Enable setjmp/longjmp handling
+ bool EnableEH; // Enable exception handling
+ bool EnableSjLj; // Enable setjmp/longjmp handling
GlobalVariable *ThrewGV;
GlobalVariable *ThrewValueGV;
GlobalVariable *TempRet0GV;
Function *ResumeF;
Function *EHTypeIDF;
+ Function *EmLongjmpF;
+ Function *EmLongjmpJmpbufF;
+ Function *SaveSetjmpF;
+ Function *TestSetjmpF;
+
// __cxa_find_matching_catch_N functions.
// Indexed by the number of clauses in an original landingpad instruction.
DenseMap<int, Function *> FindMatchingCatches;
bool runEHOnFunction(Function &F);
bool runSjLjOnFunction(Function &F);
- // Returns __cxa_find_matching_catch_N function, where N = NumClauses + 2.
- // This is because a landingpad instruction contains two more arguments,
- // a personality function and a cleanup bit, and __cxa_find_matching_catch_N
- // functions are named after the number of arguments in the original
- // landingpad instruction.
Function *getFindMatchingCatch(Module &M, unsigned NumClauses);
- Function *getInvokeWrapper(Module &M, InvokeInst *II);
+ template <typename CallOrInvoke> Value *wrapInvoke(CallOrInvoke *CI);
+ void wrapTestSetjmp(BasicBlock *BB, Instruction *InsertPt, Value *Threw,
+ Value *SetjmpTable, Value *SetjmpTableSize, Value *&Label,
+ Value *&LongjmpResult, BasicBlock *&EndBB);
+ template <typename CallOrInvoke> Function *getInvokeWrapper(CallOrInvoke *CI);
+
bool areAllExceptionsAllowed() const { return EHWhitelistSet.empty(); }
+ bool canLongjmp(Module &M, const Value *Callee) const;
+
+ void createSetThrewFunction(Module &M);
+ void createSetTempRet0Function(Module &M);
+
+ void rebuildSSA(Function &F);
public:
static char ID;
- WebAssemblyLowerEmscriptenEHSjLj(bool DoEH = true, bool DoSjLj = true)
- : ModulePass(ID), DoEH(DoEH), DoSjLj(DoSjLj), ThrewGV(nullptr),
- ThrewValueGV(nullptr), TempRet0GV(nullptr), ResumeF(nullptr),
- EHTypeIDF(nullptr) {
+ WebAssemblyLowerEmscriptenEHSjLj(bool EnableEH = true, bool EnableSjLj = true)
+ : ModulePass(ID), EnableEH(EnableEH), EnableSjLj(EnableSjLj),
+ ThrewGV(nullptr), ThrewValueGV(nullptr), TempRet0GV(nullptr),
+ ResumeF(nullptr), EHTypeIDF(nullptr), EmLongjmpF(nullptr),
+ EmLongjmpJmpbufF(nullptr), SaveSetjmpF(nullptr), TestSetjmpF(nullptr) {
EHWhitelistSet.insert(EHWhitelist.begin(), EHWhitelist.end());
}
bool runOnModule(Module &M) override;
+
+ void getAnalysisUsage(AnalysisUsage &AU) const override {
+ AU.addRequired<DominatorTreeWrapperPass>();
+ }
};
} // End anonymous namespace
"llvm_eh_typeid_for";
const char *WebAssemblyLowerEmscriptenEHSjLj::SetThrewFName = "setThrew";
const char *WebAssemblyLowerEmscriptenEHSjLj::SetTempRet0FName = "setTempRet0";
+const char *WebAssemblyLowerEmscriptenEHSjLj::EmLongjmpFName =
+ "emscripten_longjmp";
+const char *WebAssemblyLowerEmscriptenEHSjLj::EmLongjmpJmpbufFName =
+ "emscripten_longjmp_jmpbuf";
+const char *WebAssemblyLowerEmscriptenEHSjLj::SaveSetjmpFName = "saveSetjmp";
+const char *WebAssemblyLowerEmscriptenEHSjLj::TestSetjmpFName = "testSetjmp";
const char *WebAssemblyLowerEmscriptenEHSjLj::FindMatchingCatchPrefix =
"__cxa_find_matching_catch_";
const char *WebAssemblyLowerEmscriptenEHSjLj::InvokePrefix = "__invoke_";
"WebAssembly Lower Emscripten Exceptions / Setjmp / Longjmp",
false, false)
-ModulePass *llvm::createWebAssemblyLowerEmscriptenEHSjLj(bool DoEH,
- bool DoSjLj) {
- return new WebAssemblyLowerEmscriptenEHSjLj(DoEH, DoSjLj);
+ModulePass *llvm::createWebAssemblyLowerEmscriptenEHSjLj(bool EnableEH,
+ bool EnableSjLj) {
+ return new WebAssemblyLowerEmscriptenEHSjLj(EnableEH, EnableSjLj);
}
static bool canThrow(const Value *V) {
// leave setjmp and longjmp (mostly) alone, we process them properly later
if (Name == "setjmp" || Name == "longjmp")
return false;
- return true;
+ return !F->doesNotThrow();
}
// not a function, so an indirect call - can throw, we can't tell
return true;
return Sig;
}
+// Returns __cxa_find_matching_catch_N function, where N = NumClauses + 2.
+// This is because a landingpad instruction contains two more arguments, a
+// personality function and a cleanup bit, and __cxa_find_matching_catch_N
+// functions are named after the number of arguments in the original landingpad
+// instruction.
Function *
WebAssemblyLowerEmscriptenEHSjLj::getFindMatchingCatch(Module &M,
unsigned NumClauses) {
return F;
}
-Function *WebAssemblyLowerEmscriptenEHSjLj::getInvokeWrapper(Module &M,
- InvokeInst *II) {
+// Generate invoke wrapper seqence with preamble and postamble
+// Preamble:
+// __THREW__ = 0;
+// Postamble:
+// %__THREW__.val = __THREW__; __THREW__ = 0;
+// Returns %__THREW__.val, which indicates whether an exception is thrown (or
+// whether longjmp occurred), for future use.
+template <typename CallOrInvoke>
+Value *WebAssemblyLowerEmscriptenEHSjLj::wrapInvoke(CallOrInvoke *CI) {
+ LLVMContext &C = CI->getModule()->getContext();
+
+ // If we are calling a function that is noreturn, we must remove that
+ // attribute. The code we insert here does expect it to return, after we
+ // catch the exception.
+ if (CI->doesNotReturn()) {
+ if (auto *F = dyn_cast<Function>(CI->getCalledValue()))
+ F->removeFnAttr(Attribute::NoReturn);
+ CI->removeAttribute(AttributeSet::FunctionIndex, Attribute::NoReturn);
+ }
+
+ IRBuilder<> IRB(C);
+ IRB.SetInsertPoint(CI);
+
+ // Pre-invoke
+ // __THREW__ = 0;
+ IRB.CreateStore(IRB.getInt32(0), ThrewGV);
+
+ // Invoke function wrapper in JavaScript
+ SmallVector<Value *, 16> Args;
+ // Put the pointer to the callee as first argument, so it can be called
+ // within the invoke wrapper later
+ Args.push_back(CI->getCalledValue());
+ Args.append(CI->arg_begin(), CI->arg_end());
+ CallInst *NewCall = IRB.CreateCall(getInvokeWrapper(CI), Args);
+ NewCall->takeName(CI);
+ NewCall->setCallingConv(CI->getCallingConv());
+ NewCall->setDebugLoc(CI->getDebugLoc());
+
+ // Because we added the pointer to the callee as first argument, all
+ // argument attribute indices have to be incremented by one.
+ SmallVector<AttributeSet, 8> AttributesVec;
+ const AttributeSet &InvokePAL = CI->getAttributes();
+ CallSite::arg_iterator AI = CI->arg_begin();
+ unsigned i = 1; // Argument attribute index starts from 1
+ for (unsigned e = CI->getNumArgOperands(); i <= e; ++AI, ++i) {
+ if (InvokePAL.hasAttributes(i)) {
+ AttrBuilder B(InvokePAL, i);
+ AttributesVec.push_back(AttributeSet::get(C, i + 1, B));
+ }
+ }
+ // Add any return attributes.
+ if (InvokePAL.hasAttributes(AttributeSet::ReturnIndex))
+ AttributesVec.push_back(AttributeSet::get(C, InvokePAL.getRetAttributes()));
+ // Add any function attributes.
+ if (InvokePAL.hasAttributes(AttributeSet::FunctionIndex))
+ AttributesVec.push_back(AttributeSet::get(C, InvokePAL.getFnAttributes()));
+ // Reconstruct the AttributesList based on the vector we constructed.
+ AttributeSet NewCallPAL = AttributeSet::get(C, AttributesVec);
+ NewCall->setAttributes(NewCallPAL);
+
+ CI->replaceAllUsesWith(NewCall);
+
+ // Post-invoke
+ // %__THREW__.val = __THREW__; __THREW__ = 0;
+ Value *Threw = IRB.CreateLoad(ThrewGV, ThrewGV->getName() + ".val");
+ IRB.CreateStore(IRB.getInt32(0), ThrewGV);
+ return Threw;
+}
+
+// Get matching invoke wrapper based on callee signature
+template <typename CallOrInvoke>
+Function *WebAssemblyLowerEmscriptenEHSjLj::getInvokeWrapper(CallOrInvoke *CI) {
+ Module *M = CI->getModule();
SmallVector<Type *, 16> ArgTys;
- Value *Callee = II->getCalledValue();
+ Value *Callee = CI->getCalledValue();
FunctionType *CalleeFTy;
if (auto *F = dyn_cast<Function>(Callee))
CalleeFTy = F->getFunctionType();
else {
- auto *CalleeTy = dyn_cast<PointerType>(Callee->getType())->getElementType();
+ auto *CalleeTy = cast<PointerType>(Callee->getType())->getElementType();
CalleeFTy = dyn_cast<FunctionType>(CalleeTy);
}
FunctionType *FTy = FunctionType::get(CalleeFTy->getReturnType(), ArgTys,
CalleeFTy->isVarArg());
Function *F = Function::Create(FTy, GlobalValue::ExternalLinkage,
- InvokePrefix + Sig, &M);
+ InvokePrefix + Sig, M);
InvokeWrappers[Sig] = F;
return F;
}
+bool WebAssemblyLowerEmscriptenEHSjLj::canLongjmp(Module &M,
+ const Value *Callee) const {
+ if (auto *CalleeF = dyn_cast<Function>(Callee))
+ if (CalleeF->isIntrinsic())
+ return false;
+
+ // The reason we include malloc/free here is to exclude the malloc/free
+ // calls generated in setjmp prep / cleanup routines.
+ Function *SetjmpF = M.getFunction("setjmp");
+ Function *MallocF = M.getFunction("malloc");
+ Function *FreeF = M.getFunction("free");
+ if (Callee == SetjmpF || Callee == MallocF || Callee == FreeF)
+ return false;
+
+ // There are functions in JS glue code
+ if (Callee == ResumeF || Callee == EHTypeIDF || Callee == SaveSetjmpF ||
+ Callee == TestSetjmpF)
+ return false;
+
+ // __cxa_find_matching_catch_N functions cannot longjmp
+ if (Callee->getName().startswith(FindMatchingCatchPrefix))
+ return false;
+
+ // Exception-catching related functions
+ Function *BeginCatchF = M.getFunction("__cxa_begin_catch");
+ Function *EndCatchF = M.getFunction("__cxa_end_catch");
+ Function *AllocExceptionF = M.getFunction("__cxa_allocate_exception");
+ Function *ThrowF = M.getFunction("__cxa_throw");
+ Function *TerminateF = M.getFunction("__clang_call_terminate");
+ if (Callee == BeginCatchF || Callee == EndCatchF ||
+ Callee == AllocExceptionF || Callee == ThrowF || Callee == TerminateF)
+ return false;
+
+ // Otherwise we don't know
+ return true;
+}
+
+// Generate testSetjmp function call seqence with preamble and postamble.
+// The code this generates is equivalent to the following JavaScript code:
+// if (%__THREW__.val != 0 & threwValue != 0) {
+// %label = _testSetjmp(mem[%__THREW__.val], setjmpTable, setjmpTableSize);
+// if (%label == 0)
+// emscripten_longjmp(%__THREW__.val, threwValue);
+// __tempRet0 = threwValue;
+// } else {
+// %label = -1;
+// }
+// %longjmp_result = __tempRet0;
+//
+// As output parameters. returns %label, %longjmp_result, and the BB the last
+// instruction (%longjmp_result = ...) is in.
+void WebAssemblyLowerEmscriptenEHSjLj::wrapTestSetjmp(
+ BasicBlock *BB, Instruction *InsertPt, Value *Threw, Value *SetjmpTable,
+ Value *SetjmpTableSize, Value *&Label, Value *&LongjmpResult,
+ BasicBlock *&EndBB) {
+ Function *F = BB->getParent();
+ LLVMContext &C = BB->getModule()->getContext();
+ IRBuilder<> IRB(C);
+ IRB.SetInsertPoint(InsertPt);
+
+ // if (%__THREW__.val != 0 & threwValue != 0)
+ IRB.SetInsertPoint(BB);
+ BasicBlock *ThenBB1 = BasicBlock::Create(C, "if.then1", F);
+ BasicBlock *ElseBB1 = BasicBlock::Create(C, "if.else1", F);
+ BasicBlock *EndBB1 = BasicBlock::Create(C, "if.end", F);
+ Value *ThrewCmp = IRB.CreateICmpNE(Threw, IRB.getInt32(0));
+ Value *ThrewValue =
+ IRB.CreateLoad(ThrewValueGV, ThrewValueGV->getName() + ".val");
+ Value *ThrewValueCmp = IRB.CreateICmpNE(ThrewValue, IRB.getInt32(0));
+ Value *Cmp1 = IRB.CreateAnd(ThrewCmp, ThrewValueCmp, "cmp1");
+ IRB.CreateCondBr(Cmp1, ThenBB1, ElseBB1);
+
+ // %label = _testSetjmp(mem[%__THREW__.val], _setjmpTable, _setjmpTableSize);
+ // if (%label == 0)
+ IRB.SetInsertPoint(ThenBB1);
+ BasicBlock *ThenBB2 = BasicBlock::Create(C, "if.then2", F);
+ BasicBlock *EndBB2 = BasicBlock::Create(C, "if.end2", F);
+ Value *ThrewInt = IRB.CreateIntToPtr(Threw, Type::getInt32PtrTy(C),
+ Threw->getName() + ".i32p");
+ Value *LoadedThrew =
+ IRB.CreateLoad(ThrewInt, ThrewInt->getName() + ".loaded");
+ Value *ThenLabel = IRB.CreateCall(
+ TestSetjmpF, {LoadedThrew, SetjmpTable, SetjmpTableSize}, "label");
+ Value *Cmp2 = IRB.CreateICmpEQ(ThenLabel, IRB.getInt32(0));
+ IRB.CreateCondBr(Cmp2, ThenBB2, EndBB2);
+
+ // emscripten_longjmp(%__THREW__.val, threwValue);
+ IRB.SetInsertPoint(ThenBB2);
+ IRB.CreateCall(EmLongjmpF, {Threw, ThrewValue});
+ IRB.CreateUnreachable();
+
+ // __tempRet0 = threwValue;
+ IRB.SetInsertPoint(EndBB2);
+ IRB.CreateStore(ThrewValue, TempRet0GV);
+ IRB.CreateBr(EndBB1);
+
+ IRB.SetInsertPoint(ElseBB1);
+ IRB.CreateBr(EndBB1);
+
+ // longjmp_result = __tempRet0;
+ IRB.SetInsertPoint(EndBB1);
+ PHINode *LabelPHI = IRB.CreatePHI(IRB.getInt32Ty(), 2, "label");
+ LabelPHI->addIncoming(ThenLabel, EndBB2);
+
+ LabelPHI->addIncoming(IRB.getInt32(-1), ElseBB1);
+
+ // Output parameter assignment
+ Label = LabelPHI;
+ EndBB = EndBB1;
+ LongjmpResult = IRB.CreateLoad(TempRet0GV, "longjmp_result");
+}
+
+// Create setThrew function
+// function setThrew(threw, value) {
+// if (__THREW__ == 0) {
+// __THREW__ = threw;
+// __threwValue = value;
+// }
+// }
+void WebAssemblyLowerEmscriptenEHSjLj::createSetThrewFunction(Module &M) {
+ LLVMContext &C = M.getContext();
+ IRBuilder<> IRB(C);
+
+ assert(!M.getNamedGlobal(SetThrewFName) && "setThrew already exists");
+ Type *Params[] = {IRB.getInt32Ty(), IRB.getInt32Ty()};
+ FunctionType *FTy = FunctionType::get(IRB.getVoidTy(), Params, false);
+ Function *F =
+ Function::Create(FTy, GlobalValue::ExternalLinkage, SetThrewFName, &M);
+ Argument *Arg1 = &*(F->arg_begin());
+ Argument *Arg2 = &*(++F->arg_begin());
+ Arg1->setName("threw");
+ Arg2->setName("value");
+ BasicBlock *EntryBB = BasicBlock::Create(C, "entry", F);
+ BasicBlock *ThenBB = BasicBlock::Create(C, "if.then", F);
+ BasicBlock *EndBB = BasicBlock::Create(C, "if.end", F);
+
+ IRB.SetInsertPoint(EntryBB);
+ Value *Threw = IRB.CreateLoad(ThrewGV, ThrewGV->getName() + ".val");
+ Value *Cmp = IRB.CreateICmpEQ(Threw, IRB.getInt32(0), "cmp");
+ IRB.CreateCondBr(Cmp, ThenBB, EndBB);
+
+ IRB.SetInsertPoint(ThenBB);
+ IRB.CreateStore(Arg1, ThrewGV);
+ IRB.CreateStore(Arg2, ThrewValueGV);
+ IRB.CreateBr(EndBB);
+
+ IRB.SetInsertPoint(EndBB);
+ IRB.CreateRetVoid();
+}
+
+// Create setTempRet0 function
+// function setTempRet0(value) {
+// __tempRet0 = value;
+// }
+void WebAssemblyLowerEmscriptenEHSjLj::createSetTempRet0Function(Module &M) {
+ LLVMContext &C = M.getContext();
+ IRBuilder<> IRB(C);
+
+ assert(!M.getNamedGlobal(SetTempRet0FName) && "setTempRet0 already exists");
+ Type *Params[] = {IRB.getInt32Ty()};
+ FunctionType *FTy = FunctionType::get(IRB.getVoidTy(), Params, false);
+ Function *F =
+ Function::Create(FTy, GlobalValue::ExternalLinkage, SetTempRet0FName, &M);
+ F->arg_begin()->setName("value");
+ BasicBlock *EntryBB = BasicBlock::Create(C, "entry", F);
+ IRB.SetInsertPoint(EntryBB);
+ IRB.CreateStore(&*F->arg_begin(), TempRet0GV);
+ IRB.CreateRetVoid();
+}
+
+void WebAssemblyLowerEmscriptenEHSjLj::rebuildSSA(Function &F) {
+ DominatorTree &DT = getAnalysis<DominatorTreeWrapperPass>(F).getDomTree();
+ DT.recalculate(F); // CFG has been changed
+ SSAUpdater SSA;
+ for (BasicBlock &BB : F) {
+ for (Instruction &I : BB) {
+ for (auto UI = I.use_begin(), UE = I.use_end(); UI != UE;) {
+ Use &U = *UI;
+ ++UI;
+ SSA.Initialize(I.getType(), I.getName());
+ SSA.AddAvailableValue(&BB, &I);
+ Instruction *User = cast<Instruction>(U.getUser());
+ if (User->getParent() == &BB)
+ continue;
+
+ if (PHINode *UserPN = dyn_cast<PHINode>(User))
+ if (UserPN->getIncomingBlock(U) == &BB)
+ continue;
+
+ if (DT.dominates(&I, User))
+ continue;
+ SSA.RewriteUseAfterInsertions(U);
+ }
+ }
+ }
+}
+
bool WebAssemblyLowerEmscriptenEHSjLj::runOnModule(Module &M) {
LLVMContext &C = M.getContext();
IRBuilder<> IRB(C);
- IntegerType *Int1Ty = IRB.getInt1Ty();
- PointerType *Int8PtrTy = IRB.getInt8PtrTy();
- IntegerType *Int32Ty = IRB.getInt32Ty();
- Type *VoidTy = IRB.getVoidTy();
- // Create global variables __THREW__, threwValue, and tempRet0, which are
+ Function *SetjmpF = M.getFunction("setjmp");
+ Function *LongjmpF = M.getFunction("longjmp");
+ bool SetjmpUsed = SetjmpF && !SetjmpF->use_empty();
+ bool LongjmpUsed = LongjmpF && !LongjmpF->use_empty();
+ bool DoSjLj = EnableSjLj && (SetjmpUsed || LongjmpUsed);
+
+ // Create global variables __THREW__, threwValue, and __tempRet0, which are
// used in common for both exception handling and setjmp/longjmp handling
- ThrewGV =
- new GlobalVariable(M, Int1Ty, false, GlobalValue::ExternalLinkage,
- IRB.getFalse(), createGlobalValueName(M, ThrewGVName));
+ ThrewGV = new GlobalVariable(M, IRB.getInt32Ty(), false,
+ GlobalValue::ExternalLinkage, IRB.getInt32(0),
+ createGlobalValueName(M, ThrewGVName));
ThrewValueGV = new GlobalVariable(
- M, Int32Ty, false, GlobalValue::ExternalLinkage, IRB.getInt32(0),
+ M, IRB.getInt32Ty(), false, GlobalValue::ExternalLinkage, IRB.getInt32(0),
createGlobalValueName(M, ThrewValueGVName));
- TempRet0GV = new GlobalVariable(M, Int32Ty, false,
+ TempRet0GV = new GlobalVariable(M, IRB.getInt32Ty(), false,
GlobalValue::ExternalLinkage, IRB.getInt32(0),
createGlobalValueName(M, TempRet0GVName));
bool Changed = false;
// Exception handling
- if (DoEH) {
+ if (EnableEH) {
// Register __resumeException function
- FunctionType *ResumeFTy = FunctionType::get(VoidTy, Int8PtrTy, false);
+ FunctionType *ResumeFTy =
+ FunctionType::get(IRB.getVoidTy(), IRB.getInt8PtrTy(), false);
ResumeF = Function::Create(ResumeFTy, GlobalValue::ExternalLinkage,
ResumeFName, &M);
// Register llvm_eh_typeid_for function
- FunctionType *EHTypeIDTy = FunctionType::get(Int32Ty, Int8PtrTy, false);
+ FunctionType *EHTypeIDTy =
+ FunctionType::get(IRB.getInt32Ty(), IRB.getInt8PtrTy(), false);
EHTypeIDF = Function::Create(EHTypeIDTy, GlobalValue::ExternalLinkage,
EHTypeIDFName, &M);
}
}
- // TODO: Run CFGSimplify like the emscripten JSBackend?
-
// Setjmp/longjmp handling
if (DoSjLj) {
- for (Function &F : M) {
- if (F.isDeclaration())
- continue;
- Changed |= runSjLjOnFunction(F);
+ Changed = true; // We have setjmp or longjmp somewhere
+
+ Function *MallocF = M.getFunction("malloc");
+ Function *FreeF = M.getFunction("free");
+ if (!MallocF || !FreeF)
+ report_fatal_error(
+ "malloc and free must be linked into the module if setjmp is used");
+
+ // Register saveSetjmp function
+ FunctionType *SetjmpFTy = SetjmpF->getFunctionType();
+ SmallVector<Type *, 4> Params = {SetjmpFTy->getParamType(0),
+ IRB.getInt32Ty(), Type::getInt32PtrTy(C),
+ IRB.getInt32Ty()};
+ FunctionType *FTy =
+ FunctionType::get(Type::getInt32PtrTy(C), Params, false);
+ SaveSetjmpF = Function::Create(FTy, GlobalValue::ExternalLinkage,
+ SaveSetjmpFName, &M);
+
+ // Register testSetjmp function
+ Params = {IRB.getInt32Ty(), Type::getInt32PtrTy(C), IRB.getInt32Ty()};
+ FTy = FunctionType::get(IRB.getInt32Ty(), Params, false);
+ TestSetjmpF = Function::Create(FTy, GlobalValue::ExternalLinkage,
+ TestSetjmpFName, &M);
+
+ if (LongjmpF) {
+ // Replace all uses of longjmp with emscripten_longjmp_jmpbuf, which is
+ // defined in JS code
+ EmLongjmpJmpbufF = Function::Create(LongjmpF->getFunctionType(),
+ GlobalValue::ExternalLinkage,
+ EmLongjmpJmpbufFName, &M);
+
+ LongjmpF->replaceAllUsesWith(EmLongjmpJmpbufF);
+ }
+ FTy = FunctionType::get(IRB.getVoidTy(),
+ {IRB.getInt32Ty(), IRB.getInt32Ty()}, false);
+ EmLongjmpF =
+ Function::Create(FTy, GlobalValue::ExternalLinkage, EmLongjmpFName, &M);
+
+ // Only traverse functions that uses setjmp in order not to insert
+ // unnecessary prep / cleanup code in every function
+ SmallPtrSet<Function *, 8> SetjmpUsers;
+ for (User *U : SetjmpF->users()) {
+ auto *UI = cast<Instruction>(U);
+ SetjmpUsers.insert(UI->getFunction());
}
+ for (Function *F : SetjmpUsers)
+ runSjLjOnFunction(*F);
}
- if (!Changed)
+ if (!Changed) {
+ // Delete unused global variables and functions
+ ThrewGV->eraseFromParent();
+ ThrewValueGV->eraseFromParent();
+ TempRet0GV->eraseFromParent();
+ if (ResumeF)
+ ResumeF->eraseFromParent();
+ if (EHTypeIDF)
+ EHTypeIDF->eraseFromParent();
+ if (EmLongjmpF)
+ EmLongjmpF->eraseFromParent();
+ if (SaveSetjmpF)
+ SaveSetjmpF->eraseFromParent();
+ if (TestSetjmpF)
+ TestSetjmpF->eraseFromParent();
return false;
+ }
// If we have made any changes while doing exception handling or
// setjmp/longjmp handling, we have to create these functions for JavaScript
// to call.
- assert(!M.getNamedGlobal(SetThrewFName) && "setThrew already exists");
- assert(!M.getNamedGlobal(SetTempRet0FName) && "setTempRet0 already exists");
-
- // Create setThrew function
- SmallVector<Type *, 2> Params = {Int1Ty, Int32Ty};
- FunctionType *FTy = FunctionType::get(VoidTy, Params, false);
- Function *F =
- Function::Create(FTy, GlobalValue::ExternalLinkage, SetThrewFName, &M);
- Argument *Arg1 = &*(F->arg_begin());
- Argument *Arg2 = &*(++F->arg_begin());
- Arg1->setName("threw");
- Arg2->setName("value");
- BasicBlock *EntryBB = BasicBlock::Create(C, "entry", F);
- BasicBlock *ThenBB = BasicBlock::Create(C, "if.then", F);
- BasicBlock *EndBB = BasicBlock::Create(C, "if.end", F);
-
- IRB.SetInsertPoint(EntryBB);
- Value *Threw = IRB.CreateLoad(ThrewGV, ThrewGV->getName() + ".val");
- Value *Cmp = IRB.CreateICmpEQ(Threw, IRB.getFalse(), "cmp");
- IRB.CreateCondBr(Cmp, ThenBB, EndBB);
-
- IRB.SetInsertPoint(ThenBB);
- IRB.CreateStore(Arg1, ThrewGV);
- IRB.CreateStore(Arg2, ThrewValueGV);
- IRB.CreateBr(EndBB);
-
- IRB.SetInsertPoint(EndBB);
- IRB.CreateRetVoid();
-
- // Create setTempRet0 function
- Params = {Int32Ty};
- FTy = FunctionType::get(VoidTy, Params, false);
- F = Function::Create(FTy, GlobalValue::ExternalLinkage, SetTempRet0FName, &M);
- F->arg_begin()->setName("value");
- EntryBB = BasicBlock::Create(C, "entry", F);
- IRB.SetInsertPoint(EntryBB);
- IRB.CreateStore(&*F->arg_begin(), TempRet0GV);
- IRB.CreateRetVoid();
+ createSetThrewFunction(M);
+ createSetTempRet0Function(M);
return true;
}
bool NeedInvoke = AllowExceptions && canThrow(II->getCalledValue());
if (NeedInvoke) {
- // If we are calling a function that is noreturn, we must remove that
- // attribute. The code we insert here does expect it to return, after we
- // catch the exception.
- if (II->doesNotReturn()) {
- if (auto *F = dyn_cast<Function>(II->getCalledValue()))
- F->removeFnAttr(Attribute::NoReturn);
- AttributeSet NewAttrs = II->getAttributes();
- NewAttrs.removeAttribute(C, AttributeSet::FunctionIndex,
- Attribute::NoReturn);
- II->setAttributes(NewAttrs);
- }
-
- // Pre-invoke
- // __THREW__ = 0;
- IRB.CreateStore(IRB.getFalse(), ThrewGV);
-
- // Invoke function wrapper in JavaScript
- SmallVector<Value *, 16> CallArgs;
- // Put the pointer to the callee as first argument, so it can be called
- // within the invoke wrapper later
- CallArgs.push_back(II->getCalledValue());
- CallArgs.append(II->arg_begin(), II->arg_end());
- CallInst *NewCall = IRB.CreateCall(getInvokeWrapper(M, II), CallArgs);
- NewCall->takeName(II);
- NewCall->setCallingConv(II->getCallingConv());
- NewCall->setDebugLoc(II->getDebugLoc());
-
- // Because we added the pointer to the callee as first argument, all
- // argument attribute indices have to be incremented by one.
- SmallVector<AttributeSet, 8> AttributesVec;
- const AttributeSet &InvokePAL = II->getAttributes();
- CallSite::arg_iterator AI = II->arg_begin();
- unsigned i = 1; // Argument attribute index starts from 1
- for (unsigned e = II->getNumArgOperands(); i <= e; ++AI, ++i) {
- if (InvokePAL.hasAttributes(i)) {
- AttrBuilder B(InvokePAL, i);
- AttributesVec.push_back(AttributeSet::get(C, i + 1, B));
- }
- }
- // Add any return attributes.
- if (InvokePAL.hasAttributes(AttributeSet::ReturnIndex))
- AttributesVec.push_back(
- AttributeSet::get(C, InvokePAL.getRetAttributes()));
- // Add any function attributes.
- if (InvokePAL.hasAttributes(AttributeSet::FunctionIndex))
- AttributesVec.push_back(
- AttributeSet::get(C, InvokePAL.getFnAttributes()));
- // Reconstruct the AttributesList based on the vector we constructed.
- AttributeSet NewCallPAL = AttributeSet::get(C, AttributesVec);
- NewCall->setAttributes(NewCallPAL);
-
- II->replaceAllUsesWith(NewCall);
+ // Wrap invoke with invoke wrapper and generate preamble/postamble
+ Value *Threw = wrapInvoke(II);
ToErase.push_back(II);
- // Post-invoke
- // %__THREW__.val = __THREW__; __THREW__ = 0;
- Value *Threw = IRB.CreateLoad(ThrewGV, ThrewGV->getName() + ".val");
- IRB.CreateStore(IRB.getFalse(), ThrewGV);
-
// Insert a branch based on __THREW__ variable
- IRB.CreateCondBr(Threw, II->getUnwindDest(), II->getNormalDest());
+ Value *Cmp = IRB.CreateICmpEQ(Threw, IRB.getInt32(1), "cmp");
+ IRB.CreateCondBr(Cmp, II->getUnwindDest(), II->getNormalDest());
} else {
// This can't throw, and we don't need this invoke, just replace it with a
// call+branch
- SmallVector<Value *, 16> CallArgs(II->arg_begin(), II->arg_end());
- CallInst *NewCall = IRB.CreateCall(II->getCalledValue(), CallArgs);
+ SmallVector<Value *, 16> Args(II->arg_begin(), II->arg_end());
+ CallInst *NewCall = IRB.CreateCall(II->getCalledValue(), Args);
NewCall->takeName(II);
NewCall->setCallingConv(II->getCallingConv());
NewCall->setDebugLoc(II->getDebugLoc());
Value *Input = RI->getValue();
IRB.SetInsertPoint(RI);
Value *Low = IRB.CreateExtractValue(Input, 0, "low");
-
// Create a call to __resumeException function
- Value *Args[] = {Low};
- IRB.CreateCall(ResumeF, Args);
-
+ IRB.CreateCall(ResumeF, {Low});
// Add a terminator to the block
IRB.CreateUnreachable();
ToErase.push_back(RI);
// in the interface between JS and wasm, break out filter operands into
// their component elements.
if (LPI->isFilter(i)) {
- ArrayType *ATy = cast<ArrayType>(Clause->getType());
+ auto *ATy = cast<ArrayType>(Clause->getType());
for (unsigned j = 0, e = ATy->getNumElements(); j < e; ++j) {
Value *EV = IRB.CreateExtractValue(Clause, makeArrayRef(j), "filter");
FMCArgs.push_back(EV);
CallInst *FMCI = IRB.CreateCall(FMCF, FMCArgs, "fmc");
Value *Undef = UndefValue::get(LPI->getType());
Value *Pair0 = IRB.CreateInsertValue(Undef, FMCI, 0, "pair0");
- Value *TempRet0 = IRB.CreateLoad(TempRet0GV, TempRet0GV->getName() + "val");
+ Value *TempRet0 =
+ IRB.CreateLoad(TempRet0GV, TempRet0GV->getName() + ".val");
Value *Pair1 = IRB.CreateInsertValue(Pair0, TempRet0, 1, "pair1");
LPI->replaceAllUsesWith(Pair1);
}
bool WebAssemblyLowerEmscriptenEHSjLj::runSjLjOnFunction(Function &F) {
- // TODO
- return false;
+ Module &M = *F.getParent();
+ LLVMContext &C = F.getContext();
+ IRBuilder<> IRB(C);
+ SmallVector<Instruction *, 64> ToErase;
+ // Vector of %setjmpTable values
+ std::vector<Instruction *> SetjmpTableInsts;
+ // Vector of %setjmpTableSize values
+ std::vector<Instruction *> SetjmpTableSizeInsts;
+
+ // Setjmp preparation
+
+ // This instruction effectively means %setjmpTableSize = 4.
+ // We create this as an instruction intentionally, and we don't want to fold
+ // this instruction to a constant 4, because this value will be used in
+ // SSAUpdater.AddAvailableValue(...) later.
+ BasicBlock &EntryBB = F.getEntryBlock();
+ BinaryOperator *SetjmpTableSize = BinaryOperator::Create(
+ Instruction::Add, IRB.getInt32(4), IRB.getInt32(0), "setjmpTableSize",
+ &*EntryBB.getFirstInsertionPt());
+ // setjmpTable = (int *) malloc(40);
+ Instruction *SetjmpTable = CallInst::CreateMalloc(
+ SetjmpTableSize, IRB.getInt32Ty(), IRB.getInt32Ty(), IRB.getInt32(40),
+ nullptr, nullptr, "setjmpTable");
+ // setjmpTable[0] = 0;
+ IRB.SetInsertPoint(SetjmpTableSize);
+ IRB.CreateStore(IRB.getInt32(0), SetjmpTable);
+ SetjmpTableInsts.push_back(SetjmpTable);
+ SetjmpTableSizeInsts.push_back(SetjmpTableSize);
+
+ // Setjmp transformation
+ std::vector<PHINode *> SetjmpRetPHIs;
+ Function *SetjmpF = M.getFunction("setjmp");
+ for (User *U : SetjmpF->users()) {
+ auto *CI = dyn_cast<CallInst>(U);
+ if (!CI)
+ report_fatal_error("Does not support indirect calls to setjmp");
+
+ BasicBlock *BB = CI->getParent();
+ if (BB->getParent() != &F) // in other function
+ continue;
+
+ // The tail is everything right after the call, and will be reached once
+ // when setjmp is called, and later when longjmp returns to the setjmp
+ BasicBlock *Tail = SplitBlock(BB, CI->getNextNode());
+ // Add a phi to the tail, which will be the output of setjmp, which
+ // indicates if this is the first call or a longjmp back. The phi directly
+ // uses the right value based on where we arrive from
+ IRB.SetInsertPoint(Tail->getFirstNonPHI());
+ PHINode *SetjmpRet = IRB.CreatePHI(IRB.getInt32Ty(), 2, "setjmp.ret");
+
+ // setjmp initial call returns 0
+ SetjmpRet->addIncoming(IRB.getInt32(0), BB);
+ // The proper output is now this, not the setjmp call itself
+ CI->replaceAllUsesWith(SetjmpRet);
+ // longjmp returns to the setjmp will add themselves to this phi
+ SetjmpRetPHIs.push_back(SetjmpRet);
+
+ // Fix call target
+ // Our index in the function is our place in the array + 1 to avoid index
+ // 0, because index 0 means the longjmp is not ours to handle.
+ IRB.SetInsertPoint(CI);
+ Value *Args[] = {CI->getArgOperand(0), IRB.getInt32(SetjmpRetPHIs.size()),
+ SetjmpTable, SetjmpTableSize};
+ Instruction *NewSetjmpTable =
+ IRB.CreateCall(SaveSetjmpF, Args, "setjmpTable");
+ Instruction *NewSetjmpTableSize =
+ IRB.CreateLoad(TempRet0GV, "setjmpTableSize");
+ SetjmpTableInsts.push_back(NewSetjmpTable);
+ SetjmpTableSizeInsts.push_back(NewSetjmpTableSize);
+ ToErase.push_back(CI);
+ }
+
+ // Update each call that can longjmp so it can return to a setjmp where
+ // relevant.
+
+ // Because we are creating new BBs while processing and don't want to make
+ // all these newly created BBs candidates again for longjmp processing, we
+ // first make the vector of candidate BBs.
+ std::vector<BasicBlock *> BBs;
+ for (BasicBlock &BB : F)
+ BBs.push_back(&BB);
+
+ // BBs.size() will change within the loop, so we query it every time
+ for (unsigned i = 0; i < BBs.size(); i++) {
+ BasicBlock *BB = BBs[i];
+ for (Instruction &I : *BB) {
+ assert(!isa<InvokeInst>(&I));
+ auto *CI = dyn_cast<CallInst>(&I);
+ if (!CI)
+ continue;
+
+ const Value *Callee = CI->getCalledValue();
+ if (!canLongjmp(M, Callee))
+ continue;
+
+ Value *Threw = nullptr;
+ BasicBlock *Tail;
+ if (Callee->getName().startswith(InvokePrefix)) {
+ // If invoke wrapper has already been generated for this call in
+ // previous EH phase, search for the load instruction
+ // %__THREW__.val = __THREW__;
+ // in postamble after the invoke wrapper call
+ LoadInst *ThrewLI = nullptr;
+ StoreInst *ThrewResetSI = nullptr;
+ for (auto I = std::next(BasicBlock::iterator(CI)), IE = BB->end();
+ I != IE; ++I) {
+ if (auto *LI = dyn_cast<LoadInst>(I))
+ if (auto *GV = dyn_cast<GlobalVariable>(LI->getPointerOperand()))
+ if (GV == ThrewGV) {
+ Threw = ThrewLI = LI;
+ break;
+ }
+ }
+ // Search for the store instruction after the load above
+ // __THREW__ = 0;
+ for (auto I = std::next(BasicBlock::iterator(ThrewLI)), IE = BB->end();
+ I != IE; ++I) {
+ if (auto *SI = dyn_cast<StoreInst>(I))
+ if (auto *GV = dyn_cast<GlobalVariable>(SI->getPointerOperand()))
+ if (GV == ThrewGV && SI->getValueOperand() == IRB.getInt32(0)) {
+ ThrewResetSI = SI;
+ break;
+ }
+ }
+ assert(Threw && ThrewLI && "Cannot find __THREW__ load after invoke");
+ assert(ThrewResetSI && "Cannot find __THREW__ store after invoke");
+ Tail = SplitBlock(BB, ThrewResetSI->getNextNode());
+
+ } else {
+ // Wrap call with invoke wrapper and generate preamble/postamble
+ Threw = wrapInvoke(CI);
+ ToErase.push_back(CI);
+ Tail = SplitBlock(BB, CI->getNextNode());
+ }
+
+ // We need to replace the terminator in Tail - SplitBlock makes BB go
+ // straight to Tail, we need to check if a longjmp occurred, and go to the
+ // right setjmp-tail if so
+ ToErase.push_back(BB->getTerminator());
+
+ // Generate a function call to testSetjmp function and preamble/postamble
+ // code to figure out (1) whether longjmp occurred (2) if longjmp
+ // occurred, which setjmp it corresponds to
+ Value *Label = nullptr;
+ Value *LongjmpResult = nullptr;
+ BasicBlock *EndBB = nullptr;
+ wrapTestSetjmp(BB, CI, Threw, SetjmpTable, SetjmpTableSize, Label,
+ LongjmpResult, EndBB);
+ assert(Label && LongjmpResult && EndBB);
+
+ // Create switch instruction
+ IRB.SetInsertPoint(EndBB);
+ SwitchInst *SI = IRB.CreateSwitch(Label, Tail, 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(LongjmpResult, EndBB);
+ }
+
+ // We are splitting the block here, and must continue to find other calls
+ // in the block - which is now split. so continue to traverse in the Tail
+ BBs.push_back(Tail);
+ }
+ }
+
+ // Erase everything we no longer need in this function
+ for (Instruction *I : ToErase)
+ I->eraseFromParent();
+
+ // Free setjmpTable buffer before each return instruction
+ for (BasicBlock &BB : F) {
+ TerminatorInst *TI = BB.getTerminator();
+ if (isa<ReturnInst>(TI))
+ CallInst::CreateFree(SetjmpTable, TI);
+ }
+
+ // Every call to saveSetjmp can change setjmpTable and setjmpTableSize
+ // (when buffer reallocation occurs)
+ // entry:
+ // setjmpTableSize = 4;
+ // setjmpTable = (int *) malloc(40);
+ // setjmpTable[0] = 0;
+ // ...
+ // somebb:
+ // setjmpTable = saveSetjmp(buf, label, setjmpTable, setjmpTableSize);
+ // setjmpTableSize = __tempRet0;
+ // So we need to make sure the SSA for these variables is valid so that every
+ // saveSetjmp and testSetjmp calls have the correct arguments.
+ SSAUpdater SetjmpTableSSA;
+ SSAUpdater SetjmpTableSizeSSA;
+ SetjmpTableSSA.Initialize(Type::getInt32PtrTy(C), "setjmpTable");
+ SetjmpTableSizeSSA.Initialize(Type::getInt32Ty(C), "setjmpTableSize");
+ for (Instruction *I : SetjmpTableInsts)
+ SetjmpTableSSA.AddAvailableValue(I->getParent(), I);
+ for (Instruction *I : SetjmpTableSizeInsts)
+ SetjmpTableSizeSSA.AddAvailableValue(I->getParent(), I);
+
+ for (auto UI = SetjmpTable->use_begin(), UE = SetjmpTable->use_end();
+ UI != UE;) {
+ // Grab the use before incrementing the iterator.
+ Use &U = *UI;
+ // Increment the iterator before removing the use from the list.
+ ++UI;
+ if (Instruction *I = dyn_cast<Instruction>(U.getUser()))
+ if (I->getParent() != &EntryBB)
+ SetjmpTableSSA.RewriteUse(U);
+ }
+ for (auto UI = SetjmpTableSize->use_begin(), UE = SetjmpTableSize->use_end();
+ UI != UE;) {
+ Use &U = *UI;
+ ++UI;
+ if (Instruction *I = dyn_cast<Instruction>(U.getUser()))
+ if (I->getParent() != &EntryBB)
+ SetjmpTableSizeSSA.RewriteUse(U);
+ }
+
+ // Finally, our modifications to the cfg can break dominance of SSA variables.
+ // For example, in this code,
+ // if (x()) { .. setjmp() .. }
+ // if (y()) { .. longjmp() .. }
+ // We must split the longjmp block, and it can jump into the block splitted
+ // from setjmp one. But that means that when we split the setjmp block, it's
+ // first part no longer dominates its second part - there is a theoretically
+ // possible control flow path where x() is false, then y() is true and we
+ // reach the second part of the setjmp block, without ever reaching the first
+ // part. So, we rebuild SSA form here.
+ rebuildSSA(F);
+ return true;
}
--- /dev/null
+; RUN: opt < %s -wasm-lower-em-ehsjlj -S | FileCheck %s
+
+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] }
+
+@global_var = hidden global i32 0, align 4
+; CHECK-DAG: @[[__THREW__:__THREW__.*]] = global i32 0
+; CHECK-DAG: @[[THREWVALUE:__threwValue.*]] = global i32 0
+; CHECK-DAG: @[[TEMPRET0:__tempRet0.*]] = global i32 0
+
+; Test a simple setjmp - longjmp sequence
+define hidden void @setjmp_longjmp() {
+; CHECK-LABEL: @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 void @longjmp(%struct.__jmp_buf_tag* %arraydecay1, i32 1) #1
+ unreachable
+; CHECK: entry:
+; CHECK-NEXT: %[[MALLOCCALL:.*]] = tail call i8* @malloc(i32 40)
+; CHECK-NEXT: %[[SETJMP_TABLE:.*]] = bitcast i8* %[[MALLOCCALL]] to i32*
+; CHECK-NEXT: store i32 0, i32* %[[SETJMP_TABLE]]
+; CHECK-NEXT: %[[SETJMP_TABLE_SIZE:.*]] = add i32 4, 0
+; CHECK-NEXT: %[[BUF:.*]] = alloca [1 x %struct.__jmp_buf_tag]
+; CHECK-NEXT: %[[ARRAYDECAY:.*]] = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %[[BUF]], i32 0, i32 0
+; CHECK-NEXT: %[[SETJMP_TABLE1:.*]] = call i32* @saveSetjmp(%struct.__jmp_buf_tag* %[[ARRAYDECAY]], i32 1, i32* %[[SETJMP_TABLE]], i32 %[[SETJMP_TABLE_SIZE]])
+; CHECK-NEXT: %[[SETJMP_TABLE_SIZE1:.*]] = load i32, i32* @[[TEMPRET0]]
+; CHECK-NEXT: br label %entry.split
+
+; CHECK: entry.split:
+; CHECK-NEXT: phi i32 [ 0, %entry ], [ %[[LONGJMP_RESULT:.*]], %if.end ]
+; CHECK-NEXT: %[[ARRAYDECAY1:.*]] = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %[[BUF]], i32 0, i32 0
+; CHECK-NEXT: store i32 0, i32* @[[__THREW__]]
+; CHECK-NEXT: call void @"__invoke_void_%struct.__jmp_buf_tag*_i32"(void (%struct.__jmp_buf_tag*, i32)* @emscripten_longjmp_jmpbuf, %struct.__jmp_buf_tag* %[[ARRAYDECAY1]], i32 1)
+; CHECK-NEXT: %[[__THREW__VAL:.*]] = load i32, i32* @[[__THREW__]]
+; CHECK-NEXT: store i32 0, i32* @[[__THREW__]]
+; CHECK-NEXT: %[[CMP0:.*]] = icmp ne i32 %__THREW__.val, 0
+; CHECK-NEXT: %[[THREWVALUE_VAL:.*]] = load i32, i32* @[[THREWVALUE]]
+; CHECK-NEXT: %[[CMP1:.*]] = icmp ne i32 %[[THREWVALUE_VAL]], 0
+; CHECK-NEXT: %[[CMP:.*]] = and i1 %[[CMP0]], %[[CMP1]]
+; CHECK-NEXT: br i1 %[[CMP]], label %if.then1, label %if.else1
+
+; CHECK: entry.split.split:
+; CHECK-NEXT: unreachable
+
+; CHECK: if.then1:
+; CHECK-NEXT: %[[__THREW__VAL_I32P:.*]] = inttoptr i32 %[[__THREW__VAL]] to i32*
+; CHECK-NEXT: %[[__THREW__VAL_I32P_LOADED:.*]] = load i32, i32* %[[__THREW__VAL_I32P]]
+; CHECK-NEXT: %[[LABEL:.*]] = call i32 @testSetjmp(i32 %[[__THREW__VAL_I32P_LOADED]], i32* %[[SETJMP_TABLE1]], i32 %[[SETJMP_TABLE_SIZE1]])
+; CHECK-NEXT: %[[CMP:.*]] = icmp eq i32 %[[LABEL]], 0
+; CHECK-NEXT: br i1 %[[CMP]], label %if.then2, label %if.end2
+
+; CHECK: if.else1:
+; CHECK-NEXT: br label %if.end
+
+; CHECK: if.end:
+; CHECK-NEXT: %[[LABEL_PHI:.*]] = phi i32 [ %[[LABEL:.*]], %if.end2 ], [ -1, %if.else1 ]
+; CHECK-NEXT: %[[LONGJMP_RESULT]] = load i32, i32* @[[TEMPRET0]]
+; CHECK-NEXT: switch i32 %[[LABEL_PHI]], label %entry.split.split [
+; CHECK-NEXT: i32 1, label %entry.split
+; CHECK-NEXT: ]
+
+; CHECK: if.then2:
+; CHECK-NEXT: call void @emscripten_longjmp(i32 %[[__THREW__VAL]], i32 %[[THREWVALUE_VAL]])
+; CHECK-NEXT: unreachable
+
+; CHECK: if.end2:
+; CHECK-NEXT: store i32 %[[THREWVALUE_VAL]], i32* @[[TEMPRET0]]
+; CHECK-NEXT: br label %if.end
+}
+
+; Test a case of a function call (which is not longjmp) after a setjmp
+define hidden void @setjmp_other() {
+; CHECK-LABEL: @setjmp_other
+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()
+ ret void
+; CHECK: entry:
+; CHECK: %[[SETJMP_TABLE:.*]] = call i32* @saveSetjmp(
+
+; CHECK: entry.split:
+; CHECK: call void @__invoke_void(void ()* @foo)
+
+; CHECK: entry.split.split:
+; CHECK-NEXT: %[[BUF:.*]] = bitcast i32* %[[SETJMP_TABLE]] to i8*
+; CHECK-NEXT: tail call void @free(i8* %[[BUF]])
+; CHECK-NEXT: ret void
+}
+
+; Test a case when a function call is within try-catch, after a setjmp
+define hidden void @exception_and_longjmp() #3 personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) {
+; CHECK-LABEL: @exception_and_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
+ invoke void @foo()
+ to label %try.cont unwind label %lpad
+
+; CHECK: entry.split:
+; CHECK: store i32 0, i32* @[[__THREW__]]
+; CHECK-NEXT: call void @__invoke_void(void ()* @foo)
+; CHECK-NEXT: %[[__THREW__VAL:.*]] = load i32, i32* @[[__THREW__]]
+; CHECK-NEXT: store i32 0, i32* @[[__THREW__]]
+; CHECK-NEXT: %[[CMP0:.*]] = icmp ne i32 %[[__THREW__VAL]], 0
+; CHECK-NEXT: %[[THREWVALUE_VAL:.*]] = load i32, i32* @[[THREWVALUE]]
+; CHECK-NEXT: %[[CMP1:.*]] = icmp ne i32 %[[THREWVALUE_VAL]], 0
+; CHECK-NEXT: %[[CMP:.*]] = and i1 %[[CMP0]], %[[CMP1]]
+; CHECK-NEXT: br i1 %[[CMP]], label %if.then1, label %if.else1
+
+; CHECK: entry.split.split:
+; CHECK-NEXT: %[[CMP:.*]] = icmp eq i32 %[[__THREW__VAL]], 1
+; CHECK-NEXT: br i1 %[[CMP]], label %lpad, label %try.cont
+
+lpad: ; preds = %entry
+ %0 = 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
+ call void @__cxa_end_catch()
+ br label %try.cont
+
+try.cont: ; preds = %entry, %lpad
+ ret void
+}
+
+; Test SSA validity
+define hidden void @ssa(i32 %n) {
+; CHECK-LABEL: @ssa
+entry:
+ %buf = alloca [1 x %struct.__jmp_buf_tag], align 16
+ %cmp = icmp sgt i32 %n, 5
+ br i1 %cmp, label %if.then, label %if.end
+; CHECK: entry:
+; CHECK: %[[SETJMP_TABLE0:.*]] = bitcast i8*
+; CHECK: %[[SETJMP_TABLE_SIZE0:.*]] = add i32 4, 0
+
+if.then: ; preds = %entry
+ %0 = load i32, i32* @global_var, align 4
+ %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
+ store i32 %0, i32* @global_var, align 4
+ br label %if.end
+; CHECK: if.then:
+; CHECK: %[[VAR0:.*]] = load i32, i32* @global_var, align 4
+; CHECK: %[[SETJMP_TABLE1:.*]] = call i32* @saveSetjmp(
+; CHECK-NEXT: %[[SETJMP_TABLE_SIZE1:.*]] = load i32, i32* @[[TEMPRET0]]
+
+; CHECK: if.then.split:
+; CHECK: %[[VAR1:.*]] = phi i32 [ %[[VAR0]], %if.then ], [ %[[VAR2:.*]], %if.end3 ]
+; CHECK: %[[SETJMP_TABLE_SIZE2:.*]] = phi i32 [ %[[SETJMP_TABLE_SIZE1]], %if.then ], [ %[[SETJMP_TABLE_SIZE3:.*]], %if.end3 ]
+; CHECK: %[[SETJMP_TABLE2:.*]] = phi i32* [ %[[SETJMP_TABLE1]], %if.then ], [ %[[SETJMP_TABLE3:.*]], %if.end3 ]
+; CHECK: store i32 %[[VAR1]], i32* @global_var, align 4
+
+if.end: ; preds = %if.then, %entry
+ %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 5) #1
+ unreachable
+; CHECK: if.end:
+; CHECK: %[[VAR2]] = phi i32 [ %[[VAR1]], %if.then.split ], [ undef, %entry ]
+; CHECK: %[[SETJMP_TABLE_SIZE3]] = phi i32 [ %[[SETJMP_TABLE_SIZE2]], %if.then.split ], [ %[[SETJMP_TABLE_SIZE0]], %entry ]
+; CHECK: %[[SETJMP_TABLE3]] = phi i32* [ %[[SETJMP_TABLE2]], %if.then.split ], [ %[[SETJMP_TABLE0]], %entry ]
+}
+
+; Test a case when a function only calls other functions that are neither setjmp nor longjmp
+define hidden void @only_other_func() {
+entry:
+ call void @foo()
+ ret void
+; CHECK: call void @foo()
+}
+
+; Test a case when a function only calls longjmp and not setjmp
+define hidden void @only_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 void @longjmp(%struct.__jmp_buf_tag* %arraydecay, i32 5) #1
+ unreachable
+; CHECK: %[[ARRAYDECAY:.*]] = getelementptr inbounds
+; CHECK-NEXT: call void @emscripten_longjmp_jmpbuf(%struct.__jmp_buf_tag* %[[ARRAYDECAY]], i32 5) #1
+}
+
+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 functions and invoke wrappers declaration
+; CHECK-DAG: declare i32* @saveSetjmp(%struct.__jmp_buf_tag*, i32, i32*, i32)
+; CHECK-DAG: declare i32 @testSetjmp(i32, i32*, i32)
+; CHECK-DAG: declare void @emscripten_longjmp_jmpbuf(%struct.__jmp_buf_tag*, i32)
+; CHECK-DAG: declare void @emscripten_longjmp(i32, i32)
+; CHECK-DAG: declare void @__invoke_void(void ()*)
+; CHECK-DAG: declare void @"__invoke_void_%struct.__jmp_buf_tag*_i32"(void (%struct.__jmp_buf_tag*, i32)*, %struct.__jmp_buf_tag*, i32)
+
+attributes #0 = { returns_twice }
+attributes #1 = { noreturn }
+attributes #2 = { nounwind }