Index: lib/Target/WebAssembly/LLVMBuild.txt =================================================================== --- lib/Target/WebAssembly/LLVMBuild.txt +++ lib/Target/WebAssembly/LLVMBuild.txt @@ -29,5 +29,5 @@ type = Library name = WebAssemblyCodeGen parent = WebAssembly -required_libraries = Analysis AsmPrinter CodeGen Core MC Scalar SelectionDAG Support Target WebAssemblyAsmPrinter WebAssemblyDesc WebAssemblyInfo +required_libraries = Analysis AsmPrinter CodeGen Core MC Scalar SelectionDAG Support Target TransformUtils WebAssemblyAsmPrinter WebAssemblyDesc WebAssemblyInfo add_to_library_groups = WebAssembly Index: lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp =================================================================== --- lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp +++ lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp @@ -8,25 +8,61 @@ //===----------------------------------------------------------------------===// /// /// \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 occured, cxa_throw() function in JS side sets some variables +/// (see below) so we can check whether an exception occured from wasm code and +/// handle it appropriately. +/// +/// * 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 the part right +/// after the setjmp, and a new basic block is added which 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 occured from wasm code. Each block with a longjmp is also +/// split up after the longjmp call. After the longjmp call, we check whether +/// a longjmp occured, 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. +/// TODO 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 occured or not. 0 +/// means nothing occured, 1 means an exception occured, and other numbers +/// mean a longjmp occured. 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, @@ -96,13 +132,83 @@ /// 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__wrapper(buf, value) +/// emscripten_longjmp__wrapper is a JS function. +/// +/// 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 occured, __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__wrapper. +/// emscripten_longjmp__wrapper takes jmp_buf as its first argument, while +/// emscripten_longjmp takes an int. Both of them will eventually be lowered +/// to int, but here we need two signatures - we can't translate an int +/// value to a jmp_buf.) +/// Label -1 means no longjmp occured. 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/IR/NoFolder.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Transforms/Utils/BasicBlockUtils.h" +#include "llvm/Transforms/Utils/SSAUpdater.h" #include using namespace llvm; @@ -125,6 +231,10 @@ static const char *EHTypeIDFName; static const char *SetThrewFName; static const char *SetTempRet0FName; + static const char *EmLongjmpFName; + static const char *EmLongjmpWrapperFName; + static const char *SaveSetjmpFName; + static const char *TestSetjmpFName; static const char *FindMatchingCatchPrefix; static const char *InvokePrefix; @@ -136,6 +246,11 @@ GlobalVariable *TempRet0GV; Function *ResumeF; Function *EHTypeIDF; + Function *EmLongjmpF; + Function *EmLongjmpWrapperF; + Function *SaveSetjmpF; + Function *TestSetjmpF; + // __cxa_find_matching_catch_N functions. // Indexed by the number of clauses in an original landingpad instruction. DenseMap FindMatchingCatches; @@ -150,15 +265,21 @@ 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 Value *wrapInvoke(CallOrInvoke *CI); + void wrapTestSetjmp(BasicBlock *BB, Instruction *InsertPt, Value *Threw, + Value *SetjmpTable, Value *SetjmpTableSize, Value *&Label, + Value *&LongjmpResult, BasicBlock *&EndBB); + template 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; @@ -166,10 +287,15 @@ WebAssemblyLowerEmscriptenEHSjLj(bool DoEH = true, bool DoSjLj = true) : ModulePass(ID), DoEH(DoEH), DoSjLj(DoSjLj), ThrewGV(nullptr), ThrewValueGV(nullptr), TempRet0GV(nullptr), ResumeF(nullptr), - EHTypeIDF(nullptr) { + EHTypeIDF(nullptr), EmLongjmpF(nullptr), EmLongjmpWrapperF(nullptr), + SaveSetjmpF(nullptr), TestSetjmpF(nullptr) { EHWhitelistSet.insert(EHWhitelist.begin(), EHWhitelist.end()); } bool runOnModule(Module &M) override; + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.addRequired(); + } }; } // End anonymous namespace @@ -181,6 +307,12 @@ "llvm_eh_typeid_for"; const char *WebAssemblyLowerEmscriptenEHSjLj::SetThrewFName = "setThrew"; const char *WebAssemblyLowerEmscriptenEHSjLj::SetTempRet0FName = "setTempRet0"; +const char *WebAssemblyLowerEmscriptenEHSjLj::EmLongjmpFName = + "emscripten_longjmp"; +const char *WebAssemblyLowerEmscriptenEHSjLj::EmLongjmpWrapperFName = + "emscripten_longjmp__wrapper"; +const char *WebAssemblyLowerEmscriptenEHSjLj::SaveSetjmpFName = "saveSetjmp"; +const char *WebAssemblyLowerEmscriptenEHSjLj::TestSetjmpFName = "testSetjmp"; const char *WebAssemblyLowerEmscriptenEHSjLj::FindMatchingCatchPrefix = "__cxa_find_matching_catch_"; const char *WebAssemblyLowerEmscriptenEHSjLj::InvokePrefix = "__invoke_"; @@ -204,7 +336,7 @@ // 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; @@ -242,6 +374,11 @@ 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) { @@ -257,10 +394,84 @@ 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 occured), for future use. +template +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(CI->getCalledValue())) + F->removeFnAttr(Attribute::NoReturn); + AttributeSet NewAttrs = CI->getAttributes(); + NewAttrs.removeAttribute(C, AttributeSet::FunctionIndex, + Attribute::NoReturn); + CI->setAttributes(NewAttrs); + } + + IRBuilder<> IRB(C); + IRB.SetInsertPoint(CI); + + // Pre-invoke + // __THREW__ = 0; + IRB.CreateStore(IRB.getInt32(0), ThrewGV); + + // Invoke function wrapper in JavaScript + SmallVector 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 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 +Function *WebAssemblyLowerEmscriptenEHSjLj::getInvokeWrapper(CallOrInvoke *CI) { + Module *M = CI->getModule(); SmallVector ArgTys; - Value *Callee = II->getCalledValue(); + Value *Callee = CI->getCalledValue(); FunctionType *CalleeFTy; if (auto *F = dyn_cast(Callee)) CalleeFTy = F->getFunctionType(); @@ -281,28 +492,225 @@ 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(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 calls to 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) +// 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) + 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 *Cmp1 = IRB.CreateICmpNE(Threw, IRB.getInt32(0), "cmp1"); + IRB.CreateCondBr(Cmp1, ThenBB1, ElseBB1); + + // if (threwValue != 0) + IRB.SetInsertPoint(ThenBB1); + BasicBlock *ThenBB2 = BasicBlock::Create(C, "if.then2", F); + Value *ThrewValue = + IRB.CreateLoad(ThrewValueGV, ThrewValueGV->getName() + ".val"); + Value *Cmp2 = IRB.CreateICmpNE(ThrewValue, IRB.getInt32(0), "cmp2"); + IRB.CreateCondBr(Cmp2, ThenBB2, ElseBB1); + + // %label = _testSetjmp(mem[%__THREW__.val], _setjmpTable, _setjmpTableSize); + // if (%label == 0) + IRB.SetInsertPoint(ThenBB2); + BasicBlock *ThenBB3 = BasicBlock::Create(C, "if.then3", F); + Value *ThrewInt = IRB.CreateIntToPtr(Threw, Type::getInt32PtrTy(C), + Threw->getName() + ".i32p"); + Value *LoadedThrew = + IRB.CreateLoad(ThrewInt, ThrewInt->getName() + ".loaded"); + Value *Args[] = {LoadedThrew, SetjmpTable, SetjmpTableSize}; + BasicBlock *EndBB2 = BasicBlock::Create(C, "if.end2", F); + ThrewValue = IRB.CreateLoad(ThrewValueGV, ThrewValueGV->getName() + ".val"); + Value *ThenLabel = IRB.CreateCall(TestSetjmpF, Args, "label"); + Value *Cmp3 = IRB.CreateICmpEQ(ThenLabel, IRB.getInt32(0)); + IRB.CreateCondBr(Cmp3, ThenBB3, EndBB2); + + // emscripten_longjmp(%__THREW__.val, threwValue); + IRB.SetInsertPoint(ThenBB3); + IRB.CreateCall(EmLongjmpF, {Threw, ThrewValue}); + IRB.CreateBr(EndBB2); + + // 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(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(U.getUser()); + if (User->getParent() == &BB) + continue; + + if (PHINode *UserPN = dyn_cast(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 // 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)); @@ -311,12 +719,14 @@ // Exception handling if (DoEH) { // 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); @@ -327,61 +737,94 @@ } } - // TODO: Run CFGSimplify like the emscripten JSBackend? - // Setjmp/longjmp handling - if (DoSjLj) { - for (Function &F : M) { - if (F.isDeclaration()) - continue; - Changed |= runSjLjOnFunction(F); + Function *SetjmpF = M.getFunction("setjmp"); + Function *LongjmpF = M.getFunction("longjmp"); + bool SetjmpUsed = SetjmpF && !SetjmpF->use_empty(); + bool LongjmpUsed = LongjmpF && !LongjmpF->use_empty(); + + if (DoSjLj && (SetjmpUsed || LongjmpUsed)) { + Changed = true; // We have setjmp or longjmp somewhere + + // Register malloc/free if they do not exist in the module + Function *MallocF = M.getFunction("malloc"); + Function *FreeF = M.getFunction("free"); + if (!MallocF) { + FunctionType *FTy = + FunctionType::get(IRB.getInt8PtrTy(), IRB.getInt32Ty(), false); + MallocF = + Function::Create(FTy, GlobalValue::ExternalLinkage, "malloc", &M); + } + if (!FreeF) { + FunctionType *FTy = + FunctionType::get(IRB.getVoidTy(), IRB.getInt8PtrTy(), false); + FreeF = Function::Create(FTy, GlobalValue::ExternalLinkage, "free", &M); + } + + // Register saveSetjmp function + FunctionType *SetjmpFTy = SetjmpF->getFunctionType(); + SmallVector 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__wrapper, which is + // defined in JS code + EmLongjmpWrapperF = Function::Create(LongjmpF->getFunctionType(), + GlobalValue::ExternalLinkage, + EmLongjmpWrapperFName, &M); + + LongjmpF->replaceAllUsesWith(EmLongjmpWrapperF); + } + 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 SetjmpUsers; + for (User *U : SetjmpF->users()) { + auto *UI = cast(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 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; } @@ -406,73 +849,19 @@ 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(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 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 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 CallArgs(II->arg_begin(), II->arg_end()); - CallInst *NewCall = IRB.CreateCall(II->getCalledValue(), CallArgs); + SmallVector 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()); @@ -499,11 +888,8 @@ 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); @@ -548,7 +934,7 @@ // in the interface between JS and wasm, break out filter operands into // their component elements. if (LPI->isFilter(i)) { - ArrayType *ATy = cast(Clause->getType()); + auto *ATy = cast(Clause->getType()); for (unsigned j = 0, e = ATy->getNumElements(); j < e; ++j) { Value *EV = IRB.CreateExtractValue(Clause, makeArrayRef(j), "filter"); FMCArgs.push_back(EV); @@ -562,7 +948,8 @@ 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); @@ -577,6 +964,237 @@ } bool WebAssemblyLowerEmscriptenEHSjLj::runSjLjOnFunction(Function &F) { - // TODO - return false; + Module &M = *F.getParent(); + LLVMContext &C = F.getContext(); + IRBuilder<> IRB(C); + SmallVector ToErase; + // Vector of %setjmpTable values + std::vector SetjmpTableInsts; + // Vector of %setjmpTableSize values + std::vector 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. + IRBuilder NoFoldIRB(C); + BasicBlock &EntryBB = F.getEntryBlock(); + NoFoldIRB.SetInsertPoint(&EntryBB, EntryBB.getFirstInsertionPt()); + Instruction *SetjmpTableSize = cast( + NoFoldIRB.CreateAdd(IRB.getInt32(4), IRB.getInt32(0), "setjmpTableSize")); + + // setjmpTable = (int *) malloc(40); + // setjmpTable[0] = 0; + Instruction *SetjmpTable = CallInst::CreateMalloc( + SetjmpTableSize, IRB.getInt32Ty(), IRB.getInt32Ty(), IRB.getInt32(40), + nullptr, nullptr, "setjmpTable"); + IRB.SetInsertPoint(SetjmpTableSize); + IRB.CreateStore(IRB.getInt32(0), SetjmpTable); + SetjmpTableInsts.push_back(SetjmpTable); + SetjmpTableSizeInsts.push_back(SetjmpTableSize); + + // Setjmp transformation + std::vector SetjmpRetPHIs; + Function *SetjmpF = M.getFunction("setjmp"); + for (User *U : SetjmpF->users()) { + auto *CI = dyn_cast(U); + assert(CI && "Doest not support indirect call of 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, TempRet0GV->getName() + ".val"); + 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 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(!dyn_cast(&I)); + auto *CI = dyn_cast(&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(I)) + if (auto *GV = dyn_cast(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(I)) + if (auto *GV = dyn_cast(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 occured (2) if longjmp occured, + // 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(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(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(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; } Index: lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp =================================================================== --- lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp +++ lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp @@ -165,7 +165,14 @@ if (getOptLevel() != CodeGenOpt::None) addPass(createWebAssemblyOptimizeReturned()); - // Handle exceptions. + // If exception handling is not enabled, we lower invokes into calls and + // simplify CFG to delete unreachable landingpad blocks. + if (!EnableEmException) { + addPass(createLowerInvokePass()); + addPass(createCFGSimplificationPass()); + } + + // Handle exceptions and setjmp/longjmp if enabled. if (EnableEmException || EnableEmSjLj) addPass(createWebAssemblyLowerEmscriptenEHSjLj(EnableEmException, EnableEmSjLj));