Path: blob/main/contrib/llvm-project/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
35266 views
//=== WebAssemblyLowerEmscriptenEHSjLj.cpp - Lower exceptions for Emscripten =//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7///8/// \file9/// This file lowers exception-related instructions and setjmp/longjmp function10/// calls to use Emscripten's library functions. The pass uses JavaScript's try11/// and catch mechanism in case of Emscripten EH/SjLj and Wasm EH intrinsics in12/// case of Emscripten SjLJ.13///14/// * Emscripten exception handling15/// This pass lowers invokes and landingpads into library functions in JS glue16/// code. Invokes are lowered into function wrappers called invoke wrappers that17/// exist in JS side, which wraps the original function call with JS try-catch.18/// If an exception occurred, cxa_throw() function in JS side sets some19/// variables (see below) so we can check whether an exception occurred from20/// wasm code and handle it appropriately.21///22/// * Emscripten setjmp-longjmp handling23/// This pass lowers setjmp to a reasonably-performant approach for emscripten.24/// The idea is that each block with a setjmp is broken up into two parts: the25/// part containing setjmp and the part right after the setjmp. The latter part26/// is either reached from the setjmp, or later from a longjmp. To handle the27/// longjmp, all calls that might longjmp are also called using invoke wrappers28/// and thus JS / try-catch. JS longjmp() function also sets some variables so29/// we can check / whether a longjmp occurred from wasm code. Each block with a30/// function call that might longjmp is also split up after the longjmp call.31/// After the longjmp call, we check whether a longjmp occurred, and if it did,32/// which setjmp it corresponds to, and jump to the right post-setjmp block.33/// We assume setjmp-longjmp handling always run after EH handling, which means34/// we don't expect any exception-related instructions when SjLj runs.35/// FIXME Currently this scheme does not support indirect call of setjmp,36/// because of the limitation of the scheme itself. fastcomp does not support it37/// either.38///39/// In detail, this pass does following things:40///41/// 1) Assumes the existence of global variables: __THREW__, __threwValue42/// __THREW__ and __threwValue are defined in compiler-rt in Emscripten.43/// These variables are used for both exceptions and setjmp/longjmps.44/// __THREW__ indicates whether an exception or a longjmp occurred or not. 045/// means nothing occurred, 1 means an exception occurred, and other numbers46/// mean a longjmp occurred. In the case of longjmp, __THREW__ variable47/// indicates the corresponding setjmp buffer the longjmp corresponds to.48/// __threwValue is 0 for exceptions, and the argument to longjmp in case of49/// longjmp.50///51/// * Emscripten exception handling52///53/// 2) We assume the existence of setThrew and setTempRet0/getTempRet0 functions54/// at link time. setThrew exists in Emscripten's compiler-rt:55///56/// void setThrew(uintptr_t threw, int value) {57/// if (__THREW__ == 0) {58/// __THREW__ = threw;59/// __threwValue = value;60/// }61/// }62//63/// setTempRet0 is called from __cxa_find_matching_catch() in JS glue code.64/// In exception handling, getTempRet0 indicates the type of an exception65/// caught, and in setjmp/longjmp, it means the second argument to longjmp66/// function.67///68/// 3) Lower69/// invoke @func(arg1, arg2) to label %invoke.cont unwind label %lpad70/// into71/// __THREW__ = 0;72/// call @__invoke_SIG(func, arg1, arg2)73/// %__THREW__.val = __THREW__;74/// __THREW__ = 0;75/// if (%__THREW__.val == 1)76/// goto %lpad77/// else78/// goto %invoke.cont79/// SIG is a mangled string generated based on the LLVM IR-level function80/// signature. After LLVM IR types are lowered to the target wasm types,81/// the names for these wrappers will change based on wasm types as well,82/// as in invoke_vi (function takes an int and returns void). The bodies of83/// these wrappers will be generated in JS glue code, and inside those84/// wrappers we use JS try-catch to generate actual exception effects. It85/// also calls the original callee function. An example wrapper in JS code86/// would look like this:87/// function invoke_vi(index,a1) {88/// try {89/// Module["dynCall_vi"](index,a1); // This calls original callee90/// } catch(e) {91/// if (typeof e !== 'number' && e !== 'longjmp') throw e;92/// _setThrew(1, 0); // setThrew is called here93/// }94/// }95/// If an exception is thrown, __THREW__ will be set to true in a wrapper,96/// so we can jump to the right BB based on this value.97///98/// 4) Lower99/// %val = landingpad catch c1 catch c2 catch c3 ...100/// ... use %val ...101/// into102/// %fmc = call @__cxa_find_matching_catch_N(c1, c2, c3, ...)103/// %val = {%fmc, getTempRet0()}104/// ... use %val ...105/// Here N is a number calculated based on the number of clauses.106/// setTempRet0 is called from __cxa_find_matching_catch() in JS glue code.107///108/// 5) Lower109/// resume {%a, %b}110/// into111/// call @__resumeException(%a)112/// where __resumeException() is a function in JS glue code.113///114/// 6) Lower115/// call @llvm.eh.typeid.for(type) (intrinsic)116/// into117/// call @llvm_eh_typeid_for(type)118/// llvm_eh_typeid_for function will be generated in JS glue code.119///120/// * Emscripten setjmp / longjmp handling121///122/// If there are calls to longjmp()123///124/// 1) Lower125/// longjmp(env, val)126/// into127/// emscripten_longjmp(env, val)128///129/// If there are calls to setjmp()130///131/// 2) In the function entry that calls setjmp, initialize132/// functionInvocationId as follows:133///134/// functionInvocationId = alloca(4)135///136/// Note: the alloca size is not important as this pointer is137/// merely used for pointer comparisions.138///139/// 3) Lower140/// setjmp(env)141/// into142/// __wasm_setjmp(env, label, functionInvocationId)143///144/// __wasm_setjmp records the necessary info (the label and145/// functionInvocationId) to the "env".146/// A BB with setjmp is split into two after setjmp call in order to147/// make the post-setjmp BB the possible destination of longjmp BB.148///149/// 4) Lower every call that might longjmp into150/// __THREW__ = 0;151/// call @__invoke_SIG(func, arg1, arg2)152/// %__THREW__.val = __THREW__;153/// __THREW__ = 0;154/// %__threwValue.val = __threwValue;155/// if (%__THREW__.val != 0 & %__threwValue.val != 0) {156/// %label = __wasm_setjmp_test(%__THREW__.val, functionInvocationId);157/// if (%label == 0)158/// emscripten_longjmp(%__THREW__.val, %__threwValue.val);159/// setTempRet0(%__threwValue.val);160/// } else {161/// %label = -1;162/// }163/// longjmp_result = getTempRet0();164/// switch %label {165/// label 1: goto post-setjmp BB 1166/// label 2: goto post-setjmp BB 2167/// ...168/// default: goto splitted next BB169/// }170///171/// __wasm_setjmp_test examines the jmp buf to see if it was for a matching172/// setjmp call. After calling an invoke wrapper, if a longjmp occurred,173/// __THREW__ will be the address of matching jmp_buf buffer and174/// __threwValue be the second argument to longjmp.175/// __wasm_setjmp_test returns a setjmp label, a unique ID to each setjmp176/// callsite. Label 0 means this longjmp buffer does not correspond to one177/// of the setjmp callsites in this function, so in this case we just chain178/// the longjmp to the caller. Label -1 means no longjmp occurred.179/// Otherwise we jump to the right post-setjmp BB based on the label.180///181/// * Wasm setjmp / longjmp handling182/// This mode still uses some Emscripten library functions but not JavaScript's183/// try-catch mechanism. It instead uses Wasm exception handling intrinsics,184/// which will be lowered to exception handling instructions.185///186/// If there are calls to longjmp()187///188/// 1) Lower189/// longjmp(env, val)190/// into191/// __wasm_longjmp(env, val)192///193/// If there are calls to setjmp()194///195/// 2) and 3): The same as 2) and 3) in Emscripten SjLj.196/// (functionInvocationId initialization + setjmp callsite transformation)197///198/// 4) Create a catchpad with a wasm.catch() intrinsic, which returns the value199/// thrown by __wasm_longjmp function. In the runtime library, we have an200/// equivalent of the following struct:201///202/// struct __WasmLongjmpArgs {203/// void *env;204/// int val;205/// };206///207/// The thrown value here is a pointer to the struct. We use this struct to208/// transfer two values by throwing a single value. Wasm throw and catch209/// instructions are capable of throwing and catching multiple values, but210/// it also requires multivalue support that is currently not very reliable.211/// TODO Switch to throwing and catching two values without using the struct212///213/// All longjmpable function calls will be converted to an invoke that will214/// unwind to this catchpad in case a longjmp occurs. Within the catchpad, we215/// test the thrown values using __wasm_setjmp_test function as we do for216/// Emscripten SjLj. The main difference is, in Emscripten SjLj, we need to217/// transform every longjmpable callsite into a sequence of code including218/// __wasm_setjmp_test() call; in Wasm SjLj we do the testing in only one219/// place, in this catchpad.220///221/// After testing calling __wasm_setjmp_test(), if the longjmp does not222/// correspond to one of the setjmps within the current function, it rethrows223/// the longjmp by calling __wasm_longjmp(). If it corresponds to one of224/// setjmps in the function, we jump to the beginning of the function, which225/// contains a switch to each post-setjmp BB. Again, in Emscripten SjLj, this226/// switch is added for every longjmpable callsite; in Wasm SjLj we do this227/// only once at the top of the function. (after functionInvocationId228/// initialization)229///230/// The below is the pseudocode for what we have described231///232/// entry:233/// Initialize functionInvocationId234///235/// setjmp.dispatch:236/// switch %label {237/// label 1: goto post-setjmp BB 1238/// label 2: goto post-setjmp BB 2239/// ...240/// default: goto splitted next BB241/// }242/// ...243///244/// bb:245/// invoke void @foo() ;; foo is a longjmpable function246/// to label %next unwind label %catch.dispatch.longjmp247/// ...248///249/// catch.dispatch.longjmp:250/// %0 = catchswitch within none [label %catch.longjmp] unwind to caller251///252/// catch.longjmp:253/// %longjmp.args = wasm.catch() ;; struct __WasmLongjmpArgs254/// %env = load 'env' field from __WasmLongjmpArgs255/// %val = load 'val' field from __WasmLongjmpArgs256/// %label = __wasm_setjmp_test(%env, functionInvocationId);257/// if (%label == 0)258/// __wasm_longjmp(%env, %val)259/// catchret to %setjmp.dispatch260///261///===----------------------------------------------------------------------===//262263#include "MCTargetDesc/WebAssemblyMCTargetDesc.h"264#include "WebAssembly.h"265#include "WebAssemblyTargetMachine.h"266#include "llvm/ADT/StringExtras.h"267#include "llvm/CodeGen/TargetPassConfig.h"268#include "llvm/CodeGen/WasmEHFuncInfo.h"269#include "llvm/IR/DebugInfoMetadata.h"270#include "llvm/IR/Dominators.h"271#include "llvm/IR/IRBuilder.h"272#include "llvm/IR/IntrinsicsWebAssembly.h"273#include "llvm/IR/Module.h"274#include "llvm/Support/CommandLine.h"275#include "llvm/Transforms/Utils/BasicBlockUtils.h"276#include "llvm/Transforms/Utils/Local.h"277#include "llvm/Transforms/Utils/SSAUpdater.h"278#include "llvm/Transforms/Utils/SSAUpdaterBulk.h"279#include <set>280281using namespace llvm;282283#define DEBUG_TYPE "wasm-lower-em-ehsjlj"284285static cl::list<std::string>286EHAllowlist("emscripten-cxx-exceptions-allowed",287cl::desc("The list of function names in which Emscripten-style "288"exception handling is enabled (see emscripten "289"EMSCRIPTEN_CATCHING_ALLOWED options)"),290cl::CommaSeparated);291292namespace {293class WebAssemblyLowerEmscriptenEHSjLj final : public ModulePass {294bool EnableEmEH; // Enable Emscripten exception handling295bool EnableEmSjLj; // Enable Emscripten setjmp/longjmp handling296bool EnableWasmSjLj; // Enable Wasm setjmp/longjmp handling297bool DoSjLj; // Whether we actually perform setjmp/longjmp handling298299GlobalVariable *ThrewGV = nullptr; // __THREW__ (Emscripten)300GlobalVariable *ThrewValueGV = nullptr; // __threwValue (Emscripten)301Function *GetTempRet0F = nullptr; // getTempRet0() (Emscripten)302Function *SetTempRet0F = nullptr; // setTempRet0() (Emscripten)303Function *ResumeF = nullptr; // __resumeException() (Emscripten)304Function *EHTypeIDF = nullptr; // llvm.eh.typeid.for() (intrinsic)305Function *EmLongjmpF = nullptr; // emscripten_longjmp() (Emscripten)306Function *WasmSetjmpF = nullptr; // __wasm_setjmp() (Emscripten)307Function *WasmSetjmpTestF = nullptr; // __wasm_setjmp_test() (Emscripten)308Function *WasmLongjmpF = nullptr; // __wasm_longjmp() (Emscripten)309Function *CatchF = nullptr; // wasm.catch() (intrinsic)310311// type of 'struct __WasmLongjmpArgs' defined in emscripten312Type *LongjmpArgsTy = nullptr;313314// __cxa_find_matching_catch_N functions.315// Indexed by the number of clauses in an original landingpad instruction.316DenseMap<int, Function *> FindMatchingCatches;317// Map of <function signature string, invoke_ wrappers>318StringMap<Function *> InvokeWrappers;319// Set of allowed function names for exception handling320std::set<std::string> EHAllowlistSet;321// Functions that contains calls to setjmp322SmallPtrSet<Function *, 8> SetjmpUsers;323324StringRef getPassName() const override {325return "WebAssembly Lower Emscripten Exceptions";326}327328using InstVector = SmallVectorImpl<Instruction *>;329bool runEHOnFunction(Function &F);330bool runSjLjOnFunction(Function &F);331void handleLongjmpableCallsForEmscriptenSjLj(332Function &F, Instruction *FunctionInvocationId,333SmallVectorImpl<PHINode *> &SetjmpRetPHIs);334void335handleLongjmpableCallsForWasmSjLj(Function &F,336Instruction *FunctionInvocationId,337SmallVectorImpl<PHINode *> &SetjmpRetPHIs);338Function *getFindMatchingCatch(Module &M, unsigned NumClauses);339340Value *wrapInvoke(CallBase *CI);341void wrapTestSetjmp(BasicBlock *BB, DebugLoc DL, Value *Threw,342Value *FunctionInvocationId, Value *&Label,343Value *&LongjmpResult, BasicBlock *&CallEmLongjmpBB,344PHINode *&CallEmLongjmpBBThrewPHI,345PHINode *&CallEmLongjmpBBThrewValuePHI,346BasicBlock *&EndBB);347Function *getInvokeWrapper(CallBase *CI);348349bool areAllExceptionsAllowed() const { return EHAllowlistSet.empty(); }350bool supportsException(const Function *F) const {351return EnableEmEH && (areAllExceptionsAllowed() ||352EHAllowlistSet.count(std::string(F->getName())));353}354void replaceLongjmpWith(Function *LongjmpF, Function *NewF);355356void rebuildSSA(Function &F);357358public:359static char ID;360361WebAssemblyLowerEmscriptenEHSjLj()362: ModulePass(ID), EnableEmEH(WebAssembly::WasmEnableEmEH),363EnableEmSjLj(WebAssembly::WasmEnableEmSjLj),364EnableWasmSjLj(WebAssembly::WasmEnableSjLj) {365assert(!(EnableEmSjLj && EnableWasmSjLj) &&366"Two SjLj modes cannot be turned on at the same time");367assert(!(EnableEmEH && EnableWasmSjLj) &&368"Wasm SjLj should be only used with Wasm EH");369EHAllowlistSet.insert(EHAllowlist.begin(), EHAllowlist.end());370}371bool runOnModule(Module &M) override;372373void getAnalysisUsage(AnalysisUsage &AU) const override {374AU.addRequired<DominatorTreeWrapperPass>();375}376};377} // End anonymous namespace378379char WebAssemblyLowerEmscriptenEHSjLj::ID = 0;380INITIALIZE_PASS(WebAssemblyLowerEmscriptenEHSjLj, DEBUG_TYPE,381"WebAssembly Lower Emscripten Exceptions / Setjmp / Longjmp",382false, false)383384ModulePass *llvm::createWebAssemblyLowerEmscriptenEHSjLj() {385return new WebAssemblyLowerEmscriptenEHSjLj();386}387388static bool canThrow(const Value *V) {389if (const auto *F = dyn_cast<const Function>(V)) {390// Intrinsics cannot throw391if (F->isIntrinsic())392return false;393StringRef Name = F->getName();394// leave setjmp and longjmp (mostly) alone, we process them properly later395if (Name == "setjmp" || Name == "longjmp" || Name == "emscripten_longjmp")396return false;397return !F->doesNotThrow();398}399// not a function, so an indirect call - can throw, we can't tell400return true;401}402403// Get a thread-local global variable with the given name. If it doesn't exist404// declare it, which will generate an import and assume that it will exist at405// link time.406static GlobalVariable *getGlobalVariable(Module &M, Type *Ty,407WebAssemblyTargetMachine &TM,408const char *Name) {409auto *GV = dyn_cast<GlobalVariable>(M.getOrInsertGlobal(Name, Ty));410if (!GV)411report_fatal_error(Twine("unable to create global: ") + Name);412413// Variables created by this function are thread local. If the target does not414// support TLS, we depend on CoalesceFeaturesAndStripAtomics to downgrade it415// to non-thread-local ones, in which case we don't allow this object to be416// linked with other objects using shared memory.417GV->setThreadLocalMode(GlobalValue::GeneralDynamicTLSModel);418return GV;419}420421// Simple function name mangler.422// This function simply takes LLVM's string representation of parameter types423// and concatenate them with '_'. There are non-alphanumeric characters but llc424// is ok with it, and we need to postprocess these names after the lowering425// phase anyway.426static std::string getSignature(FunctionType *FTy) {427std::string Sig;428raw_string_ostream OS(Sig);429OS << *FTy->getReturnType();430for (Type *ParamTy : FTy->params())431OS << "_" << *ParamTy;432if (FTy->isVarArg())433OS << "_...";434Sig = OS.str();435erase_if(Sig, isSpace);436// When s2wasm parses .s file, a comma means the end of an argument. So a437// mangled function name can contain any character but a comma.438std::replace(Sig.begin(), Sig.end(), ',', '.');439return Sig;440}441442static Function *getEmscriptenFunction(FunctionType *Ty, const Twine &Name,443Module *M) {444Function* F = Function::Create(Ty, GlobalValue::ExternalLinkage, Name, M);445// Tell the linker that this function is expected to be imported from the446// 'env' module.447if (!F->hasFnAttribute("wasm-import-module")) {448llvm::AttrBuilder B(M->getContext());449B.addAttribute("wasm-import-module", "env");450F->addFnAttrs(B);451}452if (!F->hasFnAttribute("wasm-import-name")) {453llvm::AttrBuilder B(M->getContext());454B.addAttribute("wasm-import-name", F->getName());455F->addFnAttrs(B);456}457return F;458}459460// Returns an integer type for the target architecture's address space.461// i32 for wasm32 and i64 for wasm64.462static Type *getAddrIntType(Module *M) {463IRBuilder<> IRB(M->getContext());464return IRB.getIntNTy(M->getDataLayout().getPointerSizeInBits());465}466467// Returns an integer pointer type for the target architecture's address space.468// i32* for wasm32 and i64* for wasm64. With opaque pointers this is just a ptr469// in address space zero.470static Type *getAddrPtrType(Module *M) {471return PointerType::getUnqual(M->getContext());472}473474// Returns an integer whose type is the integer type for the target's address475// space. Returns (i32 C) for wasm32 and (i64 C) for wasm64, when C is the476// integer.477static Value *getAddrSizeInt(Module *M, uint64_t C) {478IRBuilder<> IRB(M->getContext());479return IRB.getIntN(M->getDataLayout().getPointerSizeInBits(), C);480}481482// Returns __cxa_find_matching_catch_N function, where N = NumClauses + 2.483// This is because a landingpad instruction contains two more arguments, a484// personality function and a cleanup bit, and __cxa_find_matching_catch_N485// functions are named after the number of arguments in the original landingpad486// instruction.487Function *488WebAssemblyLowerEmscriptenEHSjLj::getFindMatchingCatch(Module &M,489unsigned NumClauses) {490if (FindMatchingCatches.count(NumClauses))491return FindMatchingCatches[NumClauses];492PointerType *Int8PtrTy = PointerType::getUnqual(M.getContext());493SmallVector<Type *, 16> Args(NumClauses, Int8PtrTy);494FunctionType *FTy = FunctionType::get(Int8PtrTy, Args, false);495Function *F = getEmscriptenFunction(496FTy, "__cxa_find_matching_catch_" + Twine(NumClauses + 2), &M);497FindMatchingCatches[NumClauses] = F;498return F;499}500501// Generate invoke wrapper seqence with preamble and postamble502// Preamble:503// __THREW__ = 0;504// Postamble:505// %__THREW__.val = __THREW__; __THREW__ = 0;506// Returns %__THREW__.val, which indicates whether an exception is thrown (or507// whether longjmp occurred), for future use.508Value *WebAssemblyLowerEmscriptenEHSjLj::wrapInvoke(CallBase *CI) {509Module *M = CI->getModule();510LLVMContext &C = M->getContext();511512IRBuilder<> IRB(C);513IRB.SetInsertPoint(CI);514515// Pre-invoke516// __THREW__ = 0;517IRB.CreateStore(getAddrSizeInt(M, 0), ThrewGV);518519// Invoke function wrapper in JavaScript520SmallVector<Value *, 16> Args;521// Put the pointer to the callee as first argument, so it can be called522// within the invoke wrapper later523Args.push_back(CI->getCalledOperand());524Args.append(CI->arg_begin(), CI->arg_end());525CallInst *NewCall = IRB.CreateCall(getInvokeWrapper(CI), Args);526NewCall->takeName(CI);527NewCall->setCallingConv(CallingConv::WASM_EmscriptenInvoke);528NewCall->setDebugLoc(CI->getDebugLoc());529530// Because we added the pointer to the callee as first argument, all531// argument attribute indices have to be incremented by one.532SmallVector<AttributeSet, 8> ArgAttributes;533const AttributeList &InvokeAL = CI->getAttributes();534535// No attributes for the callee pointer.536ArgAttributes.push_back(AttributeSet());537// Copy the argument attributes from the original538for (unsigned I = 0, E = CI->arg_size(); I < E; ++I)539ArgAttributes.push_back(InvokeAL.getParamAttrs(I));540541AttrBuilder FnAttrs(CI->getContext(), InvokeAL.getFnAttrs());542if (auto Args = FnAttrs.getAllocSizeArgs()) {543// The allocsize attribute (if any) referes to parameters by index and needs544// to be adjusted.545auto [SizeArg, NEltArg] = *Args;546SizeArg += 1;547if (NEltArg)548NEltArg = *NEltArg + 1;549FnAttrs.addAllocSizeAttr(SizeArg, NEltArg);550}551// In case the callee has 'noreturn' attribute, We need to remove it, because552// we expect invoke wrappers to return.553FnAttrs.removeAttribute(Attribute::NoReturn);554555// Reconstruct the AttributesList based on the vector we constructed.556AttributeList NewCallAL = AttributeList::get(557C, AttributeSet::get(C, FnAttrs), InvokeAL.getRetAttrs(), ArgAttributes);558NewCall->setAttributes(NewCallAL);559560CI->replaceAllUsesWith(NewCall);561562// Post-invoke563// %__THREW__.val = __THREW__; __THREW__ = 0;564Value *Threw =565IRB.CreateLoad(getAddrIntType(M), ThrewGV, ThrewGV->getName() + ".val");566IRB.CreateStore(getAddrSizeInt(M, 0), ThrewGV);567return Threw;568}569570// Get matching invoke wrapper based on callee signature571Function *WebAssemblyLowerEmscriptenEHSjLj::getInvokeWrapper(CallBase *CI) {572Module *M = CI->getModule();573SmallVector<Type *, 16> ArgTys;574FunctionType *CalleeFTy = CI->getFunctionType();575576std::string Sig = getSignature(CalleeFTy);577if (InvokeWrappers.contains(Sig))578return InvokeWrappers[Sig];579580// Put the pointer to the callee as first argument581ArgTys.push_back(PointerType::getUnqual(CalleeFTy));582// Add argument types583ArgTys.append(CalleeFTy->param_begin(), CalleeFTy->param_end());584585FunctionType *FTy = FunctionType::get(CalleeFTy->getReturnType(), ArgTys,586CalleeFTy->isVarArg());587Function *F = getEmscriptenFunction(FTy, "__invoke_" + Sig, M);588InvokeWrappers[Sig] = F;589return F;590}591592static bool canLongjmp(const Value *Callee) {593if (auto *CalleeF = dyn_cast<Function>(Callee))594if (CalleeF->isIntrinsic())595return false;596597// Attempting to transform inline assembly will result in something like:598// call void @__invoke_void(void ()* asm ...)599// which is invalid because inline assembly blocks do not have addresses600// and can't be passed by pointer. The result is a crash with illegal IR.601if (isa<InlineAsm>(Callee))602return false;603StringRef CalleeName = Callee->getName();604605// TODO Include more functions or consider checking with mangled prefixes606607// The reason we include malloc/free here is to exclude the malloc/free608// calls generated in setjmp prep / cleanup routines.609if (CalleeName == "setjmp" || CalleeName == "malloc" || CalleeName == "free")610return false;611612// There are functions in Emscripten's JS glue code or compiler-rt613if (CalleeName == "__resumeException" || CalleeName == "llvm_eh_typeid_for" ||614CalleeName == "__wasm_setjmp" || CalleeName == "__wasm_setjmp_test" ||615CalleeName == "getTempRet0" || CalleeName == "setTempRet0")616return false;617618// __cxa_find_matching_catch_N functions cannot longjmp619if (Callee->getName().starts_with("__cxa_find_matching_catch_"))620return false;621622// Exception-catching related functions623//624// We intentionally treat __cxa_end_catch longjmpable in Wasm SjLj even though625// it surely cannot longjmp, in order to maintain the unwind relationship from626// all existing catchpads (and calls within them) to catch.dispatch.longjmp.627//628// In Wasm EH + Wasm SjLj, we629// 1. Make all catchswitch and cleanuppad that unwind to caller unwind to630// catch.dispatch.longjmp instead631// 2. Convert all longjmpable calls to invokes that unwind to632// catch.dispatch.longjmp633// But catchswitch BBs are removed in isel, so if an EH catchswitch (generated634// from an exception)'s catchpad does not contain any calls that are converted635// into invokes unwinding to catch.dispatch.longjmp, this unwind relationship636// (EH catchswitch BB -> catch.dispatch.longjmp BB) is lost and637// catch.dispatch.longjmp BB can be placed before the EH catchswitch BB in638// CFGSort.639// int ret = setjmp(buf);640// try {641// foo(); // longjmps642// } catch (...) {643// }644// Then in this code, if 'foo' longjmps, it first unwinds to 'catch (...)'645// catchswitch, and is not caught by that catchswitch because it is a longjmp,646// then it should next unwind to catch.dispatch.longjmp BB. But if this 'catch647// (...)' catchswitch -> catch.dispatch.longjmp unwind relationship is lost,648// it will not unwind to catch.dispatch.longjmp, producing an incorrect649// result.650//651// Every catchpad generated by Wasm C++ contains __cxa_end_catch, so we652// intentionally treat it as longjmpable to work around this problem. This is653// a hacky fix but an easy one.654//655// The comment block in findWasmUnwindDestinations() in656// SelectionDAGBuilder.cpp is addressing a similar problem.657if (CalleeName == "__cxa_end_catch")658return WebAssembly::WasmEnableSjLj;659if (CalleeName == "__cxa_begin_catch" ||660CalleeName == "__cxa_allocate_exception" || CalleeName == "__cxa_throw" ||661CalleeName == "__clang_call_terminate")662return false;663664// std::terminate, which is generated when another exception occurs while665// handling an exception, cannot longjmp.666if (CalleeName == "_ZSt9terminatev")667return false;668669// Otherwise we don't know670return true;671}672673static bool isEmAsmCall(const Value *Callee) {674StringRef CalleeName = Callee->getName();675// This is an exhaustive list from Emscripten's <emscripten/em_asm.h>.676return CalleeName == "emscripten_asm_const_int" ||677CalleeName == "emscripten_asm_const_double" ||678CalleeName == "emscripten_asm_const_int_sync_on_main_thread" ||679CalleeName == "emscripten_asm_const_double_sync_on_main_thread" ||680CalleeName == "emscripten_asm_const_async_on_main_thread";681}682683// Generate __wasm_setjmp_test function call seqence with preamble and684// postamble. The code this generates is equivalent to the following685// JavaScript code:686// %__threwValue.val = __threwValue;687// if (%__THREW__.val != 0 & %__threwValue.val != 0) {688// %label = __wasm_setjmp_test(%__THREW__.val, functionInvocationId);689// if (%label == 0)690// emscripten_longjmp(%__THREW__.val, %__threwValue.val);691// setTempRet0(%__threwValue.val);692// } else {693// %label = -1;694// }695// %longjmp_result = getTempRet0();696//697// As output parameters. returns %label, %longjmp_result, and the BB the last698// instruction (%longjmp_result = ...) is in.699void WebAssemblyLowerEmscriptenEHSjLj::wrapTestSetjmp(700BasicBlock *BB, DebugLoc DL, Value *Threw, Value *FunctionInvocationId,701Value *&Label, Value *&LongjmpResult, BasicBlock *&CallEmLongjmpBB,702PHINode *&CallEmLongjmpBBThrewPHI, PHINode *&CallEmLongjmpBBThrewValuePHI,703BasicBlock *&EndBB) {704Function *F = BB->getParent();705Module *M = F->getParent();706LLVMContext &C = M->getContext();707IRBuilder<> IRB(C);708IRB.SetCurrentDebugLocation(DL);709710// if (%__THREW__.val != 0 & %__threwValue.val != 0)711IRB.SetInsertPoint(BB);712BasicBlock *ThenBB1 = BasicBlock::Create(C, "if.then1", F);713BasicBlock *ElseBB1 = BasicBlock::Create(C, "if.else1", F);714BasicBlock *EndBB1 = BasicBlock::Create(C, "if.end", F);715Value *ThrewCmp = IRB.CreateICmpNE(Threw, getAddrSizeInt(M, 0));716Value *ThrewValue = IRB.CreateLoad(IRB.getInt32Ty(), ThrewValueGV,717ThrewValueGV->getName() + ".val");718Value *ThrewValueCmp = IRB.CreateICmpNE(ThrewValue, IRB.getInt32(0));719Value *Cmp1 = IRB.CreateAnd(ThrewCmp, ThrewValueCmp, "cmp1");720IRB.CreateCondBr(Cmp1, ThenBB1, ElseBB1);721722// Generate call.em.longjmp BB once and share it within the function723if (!CallEmLongjmpBB) {724// emscripten_longjmp(%__THREW__.val, %__threwValue.val);725CallEmLongjmpBB = BasicBlock::Create(C, "call.em.longjmp", F);726IRB.SetInsertPoint(CallEmLongjmpBB);727CallEmLongjmpBBThrewPHI = IRB.CreatePHI(getAddrIntType(M), 4, "threw.phi");728CallEmLongjmpBBThrewValuePHI =729IRB.CreatePHI(IRB.getInt32Ty(), 4, "threwvalue.phi");730CallEmLongjmpBBThrewPHI->addIncoming(Threw, ThenBB1);731CallEmLongjmpBBThrewValuePHI->addIncoming(ThrewValue, ThenBB1);732IRB.CreateCall(EmLongjmpF,733{CallEmLongjmpBBThrewPHI, CallEmLongjmpBBThrewValuePHI});734IRB.CreateUnreachable();735} else {736CallEmLongjmpBBThrewPHI->addIncoming(Threw, ThenBB1);737CallEmLongjmpBBThrewValuePHI->addIncoming(ThrewValue, ThenBB1);738}739740// %label = __wasm_setjmp_test(%__THREW__.val, functionInvocationId);741// if (%label == 0)742IRB.SetInsertPoint(ThenBB1);743BasicBlock *EndBB2 = BasicBlock::Create(C, "if.end2", F);744Value *ThrewPtr =745IRB.CreateIntToPtr(Threw, getAddrPtrType(M), Threw->getName() + ".p");746Value *ThenLabel = IRB.CreateCall(WasmSetjmpTestF,747{ThrewPtr, FunctionInvocationId}, "label");748Value *Cmp2 = IRB.CreateICmpEQ(ThenLabel, IRB.getInt32(0));749IRB.CreateCondBr(Cmp2, CallEmLongjmpBB, EndBB2);750751// setTempRet0(%__threwValue.val);752IRB.SetInsertPoint(EndBB2);753IRB.CreateCall(SetTempRet0F, ThrewValue);754IRB.CreateBr(EndBB1);755756IRB.SetInsertPoint(ElseBB1);757IRB.CreateBr(EndBB1);758759// longjmp_result = getTempRet0();760IRB.SetInsertPoint(EndBB1);761PHINode *LabelPHI = IRB.CreatePHI(IRB.getInt32Ty(), 2, "label");762LabelPHI->addIncoming(ThenLabel, EndBB2);763764LabelPHI->addIncoming(IRB.getInt32(-1), ElseBB1);765766// Output parameter assignment767Label = LabelPHI;768EndBB = EndBB1;769LongjmpResult = IRB.CreateCall(GetTempRet0F, std::nullopt, "longjmp_result");770}771772void WebAssemblyLowerEmscriptenEHSjLj::rebuildSSA(Function &F) {773DominatorTree &DT = getAnalysis<DominatorTreeWrapperPass>(F).getDomTree();774DT.recalculate(F); // CFG has been changed775776SSAUpdaterBulk SSA;777for (BasicBlock &BB : F) {778for (Instruction &I : BB) {779unsigned VarID = SSA.AddVariable(I.getName(), I.getType());780// If a value is defined by an invoke instruction, it is only available in781// its normal destination and not in its unwind destination.782if (auto *II = dyn_cast<InvokeInst>(&I))783SSA.AddAvailableValue(VarID, II->getNormalDest(), II);784else785SSA.AddAvailableValue(VarID, &BB, &I);786for (auto &U : I.uses()) {787auto *User = cast<Instruction>(U.getUser());788if (auto *UserPN = dyn_cast<PHINode>(User))789if (UserPN->getIncomingBlock(U) == &BB)790continue;791if (DT.dominates(&I, User))792continue;793SSA.AddUse(VarID, &U);794}795}796}797SSA.RewriteAllUses(&DT);798}799800// Replace uses of longjmp with a new longjmp function in Emscripten library.801// In Emscripten SjLj, the new function is802// void emscripten_longjmp(uintptr_t, i32)803// In Wasm SjLj, the new function is804// void __wasm_longjmp(i8*, i32)805// Because the original libc longjmp function takes (jmp_buf*, i32), we need a806// ptrtoint/bitcast instruction here to make the type match. jmp_buf* will807// eventually be lowered to i32/i64 in the wasm backend.808void WebAssemblyLowerEmscriptenEHSjLj::replaceLongjmpWith(Function *LongjmpF,809Function *NewF) {810assert(NewF == EmLongjmpF || NewF == WasmLongjmpF);811Module *M = LongjmpF->getParent();812SmallVector<CallInst *, 8> ToErase;813LLVMContext &C = LongjmpF->getParent()->getContext();814IRBuilder<> IRB(C);815816// For calls to longjmp, replace it with emscripten_longjmp/__wasm_longjmp and817// cast its first argument (jmp_buf*) appropriately818for (User *U : LongjmpF->users()) {819auto *CI = dyn_cast<CallInst>(U);820if (CI && CI->getCalledFunction() == LongjmpF) {821IRB.SetInsertPoint(CI);822Value *Env = nullptr;823if (NewF == EmLongjmpF)824Env =825IRB.CreatePtrToInt(CI->getArgOperand(0), getAddrIntType(M), "env");826else // WasmLongjmpF827Env = IRB.CreateBitCast(CI->getArgOperand(0), IRB.getPtrTy(), "env");828IRB.CreateCall(NewF, {Env, CI->getArgOperand(1)});829ToErase.push_back(CI);830}831}832for (auto *I : ToErase)833I->eraseFromParent();834835// If we have any remaining uses of longjmp's function pointer, replace it836// with (void(*)(jmp_buf*, int))emscripten_longjmp / __wasm_longjmp.837if (!LongjmpF->uses().empty()) {838Value *NewLongjmp =839IRB.CreateBitCast(NewF, LongjmpF->getType(), "longjmp.cast");840LongjmpF->replaceAllUsesWith(NewLongjmp);841}842}843844static bool containsLongjmpableCalls(const Function *F) {845for (const auto &BB : *F)846for (const auto &I : BB)847if (const auto *CB = dyn_cast<CallBase>(&I))848if (canLongjmp(CB->getCalledOperand()))849return true;850return false;851}852853// When a function contains a setjmp call but not other calls that can longjmp,854// we don't do setjmp transformation for that setjmp. But we need to convert the855// setjmp calls into "i32 0" so they don't cause link time errors. setjmp always856// returns 0 when called directly.857static void nullifySetjmp(Function *F) {858Module &M = *F->getParent();859IRBuilder<> IRB(M.getContext());860Function *SetjmpF = M.getFunction("setjmp");861SmallVector<Instruction *, 1> ToErase;862863for (User *U : make_early_inc_range(SetjmpF->users())) {864auto *CB = cast<CallBase>(U);865BasicBlock *BB = CB->getParent();866if (BB->getParent() != F) // in other function867continue;868CallInst *CI = nullptr;869// setjmp cannot throw. So if it is an invoke, lower it to a call870if (auto *II = dyn_cast<InvokeInst>(CB))871CI = llvm::changeToCall(II);872else873CI = cast<CallInst>(CB);874ToErase.push_back(CI);875CI->replaceAllUsesWith(IRB.getInt32(0));876}877for (auto *I : ToErase)878I->eraseFromParent();879}880881bool WebAssemblyLowerEmscriptenEHSjLj::runOnModule(Module &M) {882LLVM_DEBUG(dbgs() << "********** Lower Emscripten EH & SjLj **********\n");883884LLVMContext &C = M.getContext();885IRBuilder<> IRB(C);886887Function *SetjmpF = M.getFunction("setjmp");888Function *LongjmpF = M.getFunction("longjmp");889890// In some platforms _setjmp and _longjmp are used instead. Change these to891// use setjmp/longjmp instead, because we later detect these functions by892// their names.893Function *SetjmpF2 = M.getFunction("_setjmp");894Function *LongjmpF2 = M.getFunction("_longjmp");895if (SetjmpF2) {896if (SetjmpF) {897if (SetjmpF->getFunctionType() != SetjmpF2->getFunctionType())898report_fatal_error("setjmp and _setjmp have different function types");899} else {900SetjmpF = Function::Create(SetjmpF2->getFunctionType(),901GlobalValue::ExternalLinkage, "setjmp", M);902}903SetjmpF2->replaceAllUsesWith(SetjmpF);904}905if (LongjmpF2) {906if (LongjmpF) {907if (LongjmpF->getFunctionType() != LongjmpF2->getFunctionType())908report_fatal_error(909"longjmp and _longjmp have different function types");910} else {911LongjmpF = Function::Create(LongjmpF2->getFunctionType(),912GlobalValue::ExternalLinkage, "setjmp", M);913}914LongjmpF2->replaceAllUsesWith(LongjmpF);915}916917auto *TPC = getAnalysisIfAvailable<TargetPassConfig>();918assert(TPC && "Expected a TargetPassConfig");919auto &TM = TPC->getTM<WebAssemblyTargetMachine>();920921// Declare (or get) global variables __THREW__, __threwValue, and922// getTempRet0/setTempRet0 function which are used in common for both923// exception handling and setjmp/longjmp handling924ThrewGV = getGlobalVariable(M, getAddrIntType(&M), TM, "__THREW__");925ThrewValueGV = getGlobalVariable(M, IRB.getInt32Ty(), TM, "__threwValue");926GetTempRet0F = getEmscriptenFunction(927FunctionType::get(IRB.getInt32Ty(), false), "getTempRet0", &M);928SetTempRet0F = getEmscriptenFunction(929FunctionType::get(IRB.getVoidTy(), IRB.getInt32Ty(), false),930"setTempRet0", &M);931GetTempRet0F->setDoesNotThrow();932SetTempRet0F->setDoesNotThrow();933934bool Changed = false;935936// Function registration for exception handling937if (EnableEmEH) {938// Register __resumeException function939FunctionType *ResumeFTy =940FunctionType::get(IRB.getVoidTy(), IRB.getPtrTy(), false);941ResumeF = getEmscriptenFunction(ResumeFTy, "__resumeException", &M);942ResumeF->addFnAttr(Attribute::NoReturn);943944// Register llvm_eh_typeid_for function945FunctionType *EHTypeIDTy =946FunctionType::get(IRB.getInt32Ty(), IRB.getPtrTy(), false);947EHTypeIDF = getEmscriptenFunction(EHTypeIDTy, "llvm_eh_typeid_for", &M);948}949950// Functions that contains calls to setjmp but don't have other longjmpable951// calls within them.952SmallPtrSet<Function *, 4> SetjmpUsersToNullify;953954if ((EnableEmSjLj || EnableWasmSjLj) && SetjmpF) {955// Precompute setjmp users956for (User *U : SetjmpF->users()) {957if (auto *CB = dyn_cast<CallBase>(U)) {958auto *UserF = CB->getFunction();959// If a function that calls setjmp does not contain any other calls that960// can longjmp, we don't need to do any transformation on that function,961// so can ignore it962if (containsLongjmpableCalls(UserF))963SetjmpUsers.insert(UserF);964else965SetjmpUsersToNullify.insert(UserF);966} else {967std::string S;968raw_string_ostream SS(S);969SS << *U;970report_fatal_error(Twine("Indirect use of setjmp is not supported: ") +971SS.str());972}973}974}975976bool SetjmpUsed = SetjmpF && !SetjmpUsers.empty();977bool LongjmpUsed = LongjmpF && !LongjmpF->use_empty();978DoSjLj = (EnableEmSjLj | EnableWasmSjLj) && (SetjmpUsed || LongjmpUsed);979980// Function registration and data pre-gathering for setjmp/longjmp handling981if (DoSjLj) {982assert(EnableEmSjLj || EnableWasmSjLj);983if (EnableEmSjLj) {984// Register emscripten_longjmp function985FunctionType *FTy = FunctionType::get(986IRB.getVoidTy(), {getAddrIntType(&M), IRB.getInt32Ty()}, false);987EmLongjmpF = getEmscriptenFunction(FTy, "emscripten_longjmp", &M);988EmLongjmpF->addFnAttr(Attribute::NoReturn);989} else { // EnableWasmSjLj990Type *Int8PtrTy = IRB.getPtrTy();991// Register __wasm_longjmp function, which calls __builtin_wasm_longjmp.992FunctionType *FTy = FunctionType::get(993IRB.getVoidTy(), {Int8PtrTy, IRB.getInt32Ty()}, false);994WasmLongjmpF = getEmscriptenFunction(FTy, "__wasm_longjmp", &M);995WasmLongjmpF->addFnAttr(Attribute::NoReturn);996}997998if (SetjmpF) {999Type *Int8PtrTy = IRB.getPtrTy();1000Type *Int32PtrTy = IRB.getPtrTy();1001Type *Int32Ty = IRB.getInt32Ty();10021003// Register __wasm_setjmp function1004FunctionType *SetjmpFTy = SetjmpF->getFunctionType();1005FunctionType *FTy = FunctionType::get(1006IRB.getVoidTy(), {SetjmpFTy->getParamType(0), Int32Ty, Int32PtrTy},1007false);1008WasmSetjmpF = getEmscriptenFunction(FTy, "__wasm_setjmp", &M);10091010// Register __wasm_setjmp_test function1011FTy = FunctionType::get(Int32Ty, {Int32PtrTy, Int32PtrTy}, false);1012WasmSetjmpTestF = getEmscriptenFunction(FTy, "__wasm_setjmp_test", &M);10131014// wasm.catch() will be lowered down to wasm 'catch' instruction in1015// instruction selection.1016CatchF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_catch);1017// Type for struct __WasmLongjmpArgs1018LongjmpArgsTy = StructType::get(Int8PtrTy, // env1019Int32Ty // val1020);1021}1022}10231024// Exception handling transformation1025if (EnableEmEH) {1026for (Function &F : M) {1027if (F.isDeclaration())1028continue;1029Changed |= runEHOnFunction(F);1030}1031}10321033// Setjmp/longjmp handling transformation1034if (DoSjLj) {1035Changed = true; // We have setjmp or longjmp somewhere1036if (LongjmpF)1037replaceLongjmpWith(LongjmpF, EnableEmSjLj ? EmLongjmpF : WasmLongjmpF);1038// Only traverse functions that uses setjmp in order not to insert1039// unnecessary prep / cleanup code in every function1040if (SetjmpF)1041for (Function *F : SetjmpUsers)1042runSjLjOnFunction(*F);1043}10441045// Replace unnecessary setjmp calls with 01046if ((EnableEmSjLj || EnableWasmSjLj) && !SetjmpUsersToNullify.empty()) {1047Changed = true;1048assert(SetjmpF);1049for (Function *F : SetjmpUsersToNullify)1050nullifySetjmp(F);1051}10521053// Delete unused global variables and functions1054for (auto *V : {ThrewGV, ThrewValueGV})1055if (V && V->use_empty())1056V->eraseFromParent();1057for (auto *V : {GetTempRet0F, SetTempRet0F, ResumeF, EHTypeIDF, EmLongjmpF,1058WasmSetjmpF, WasmSetjmpTestF, WasmLongjmpF, CatchF})1059if (V && V->use_empty())1060V->eraseFromParent();10611062return Changed;1063}10641065bool WebAssemblyLowerEmscriptenEHSjLj::runEHOnFunction(Function &F) {1066Module &M = *F.getParent();1067LLVMContext &C = F.getContext();1068IRBuilder<> IRB(C);1069bool Changed = false;1070SmallVector<Instruction *, 64> ToErase;1071SmallPtrSet<LandingPadInst *, 32> LandingPads;10721073// rethrow.longjmp BB that will be shared within the function.1074BasicBlock *RethrowLongjmpBB = nullptr;1075// PHI node for the loaded value of __THREW__ global variable in1076// rethrow.longjmp BB1077PHINode *RethrowLongjmpBBThrewPHI = nullptr;10781079for (BasicBlock &BB : F) {1080auto *II = dyn_cast<InvokeInst>(BB.getTerminator());1081if (!II)1082continue;1083Changed = true;1084LandingPads.insert(II->getLandingPadInst());1085IRB.SetInsertPoint(II);10861087const Value *Callee = II->getCalledOperand();1088bool NeedInvoke = supportsException(&F) && canThrow(Callee);1089if (NeedInvoke) {1090// Wrap invoke with invoke wrapper and generate preamble/postamble1091Value *Threw = wrapInvoke(II);1092ToErase.push_back(II);10931094// If setjmp/longjmp handling is enabled, the thrown value can be not an1095// exception but a longjmp. If the current function contains calls to1096// setjmp, it will be appropriately handled in runSjLjOnFunction. But even1097// if the function does not contain setjmp calls, we shouldn't silently1098// ignore longjmps; we should rethrow them so they can be correctly1099// handled in somewhere up the call chain where setjmp is. __THREW__'s1100// value is 0 when nothing happened, 1 when an exception is thrown, and1101// other values when longjmp is thrown.1102//1103// if (%__THREW__.val == 0 || %__THREW__.val == 1)1104// goto %tail1105// else1106// goto %longjmp.rethrow1107//1108// rethrow.longjmp: ;; This is longjmp. Rethrow it1109// %__threwValue.val = __threwValue1110// emscripten_longjmp(%__THREW__.val, %__threwValue.val);1111//1112// tail: ;; Nothing happened or an exception is thrown1113// ... Continue exception handling ...1114if (DoSjLj && EnableEmSjLj && !SetjmpUsers.count(&F) &&1115canLongjmp(Callee)) {1116// Create longjmp.rethrow BB once and share it within the function1117if (!RethrowLongjmpBB) {1118RethrowLongjmpBB = BasicBlock::Create(C, "rethrow.longjmp", &F);1119IRB.SetInsertPoint(RethrowLongjmpBB);1120RethrowLongjmpBBThrewPHI =1121IRB.CreatePHI(getAddrIntType(&M), 4, "threw.phi");1122RethrowLongjmpBBThrewPHI->addIncoming(Threw, &BB);1123Value *ThrewValue = IRB.CreateLoad(IRB.getInt32Ty(), ThrewValueGV,1124ThrewValueGV->getName() + ".val");1125IRB.CreateCall(EmLongjmpF, {RethrowLongjmpBBThrewPHI, ThrewValue});1126IRB.CreateUnreachable();1127} else {1128RethrowLongjmpBBThrewPHI->addIncoming(Threw, &BB);1129}11301131IRB.SetInsertPoint(II); // Restore the insert point back1132BasicBlock *Tail = BasicBlock::Create(C, "tail", &F);1133Value *CmpEqOne =1134IRB.CreateICmpEQ(Threw, getAddrSizeInt(&M, 1), "cmp.eq.one");1135Value *CmpEqZero =1136IRB.CreateICmpEQ(Threw, getAddrSizeInt(&M, 0), "cmp.eq.zero");1137Value *Or = IRB.CreateOr(CmpEqZero, CmpEqOne, "or");1138IRB.CreateCondBr(Or, Tail, RethrowLongjmpBB);1139IRB.SetInsertPoint(Tail);1140BB.replaceSuccessorsPhiUsesWith(&BB, Tail);1141}11421143// Insert a branch based on __THREW__ variable1144Value *Cmp = IRB.CreateICmpEQ(Threw, getAddrSizeInt(&M, 1), "cmp");1145IRB.CreateCondBr(Cmp, II->getUnwindDest(), II->getNormalDest());11461147} else {1148// This can't throw, and we don't need this invoke, just replace it with a1149// call+branch1150changeToCall(II);1151}1152}11531154// Process resume instructions1155for (BasicBlock &BB : F) {1156// Scan the body of the basic block for resumes1157for (Instruction &I : BB) {1158auto *RI = dyn_cast<ResumeInst>(&I);1159if (!RI)1160continue;1161Changed = true;11621163// Split the input into legal values1164Value *Input = RI->getValue();1165IRB.SetInsertPoint(RI);1166Value *Low = IRB.CreateExtractValue(Input, 0, "low");1167// Create a call to __resumeException function1168IRB.CreateCall(ResumeF, {Low});1169// Add a terminator to the block1170IRB.CreateUnreachable();1171ToErase.push_back(RI);1172}1173}11741175// Process llvm.eh.typeid.for intrinsics1176for (BasicBlock &BB : F) {1177for (Instruction &I : BB) {1178auto *CI = dyn_cast<CallInst>(&I);1179if (!CI)1180continue;1181const Function *Callee = CI->getCalledFunction();1182if (!Callee)1183continue;1184if (Callee->getIntrinsicID() != Intrinsic::eh_typeid_for)1185continue;1186Changed = true;11871188IRB.SetInsertPoint(CI);1189CallInst *NewCI =1190IRB.CreateCall(EHTypeIDF, CI->getArgOperand(0), "typeid");1191CI->replaceAllUsesWith(NewCI);1192ToErase.push_back(CI);1193}1194}11951196// Look for orphan landingpads, can occur in blocks with no predecessors1197for (BasicBlock &BB : F) {1198Instruction *I = BB.getFirstNonPHI();1199if (auto *LPI = dyn_cast<LandingPadInst>(I))1200LandingPads.insert(LPI);1201}1202Changed |= !LandingPads.empty();12031204// Handle all the landingpad for this function together, as multiple invokes1205// may share a single lp1206for (LandingPadInst *LPI : LandingPads) {1207IRB.SetInsertPoint(LPI);1208SmallVector<Value *, 16> FMCArgs;1209for (unsigned I = 0, E = LPI->getNumClauses(); I < E; ++I) {1210Constant *Clause = LPI->getClause(I);1211// TODO Handle filters (= exception specifications).1212// https://github.com/llvm/llvm-project/issues/497401213if (LPI->isCatch(I))1214FMCArgs.push_back(Clause);1215}12161217// Create a call to __cxa_find_matching_catch_N function1218Function *FMCF = getFindMatchingCatch(M, FMCArgs.size());1219CallInst *FMCI = IRB.CreateCall(FMCF, FMCArgs, "fmc");1220Value *Poison = PoisonValue::get(LPI->getType());1221Value *Pair0 = IRB.CreateInsertValue(Poison, FMCI, 0, "pair0");1222Value *TempRet0 = IRB.CreateCall(GetTempRet0F, std::nullopt, "tempret0");1223Value *Pair1 = IRB.CreateInsertValue(Pair0, TempRet0, 1, "pair1");12241225LPI->replaceAllUsesWith(Pair1);1226ToErase.push_back(LPI);1227}12281229// Erase everything we no longer need in this function1230for (Instruction *I : ToErase)1231I->eraseFromParent();12321233return Changed;1234}12351236// This tries to get debug info from the instruction before which a new1237// instruction will be inserted, and if there's no debug info in that1238// instruction, tries to get the info instead from the previous instruction (if1239// any). If none of these has debug info and a DISubprogram is provided, it1240// creates a dummy debug info with the first line of the function, because IR1241// verifier requires all inlinable callsites should have debug info when both a1242// caller and callee have DISubprogram. If none of these conditions are met,1243// returns empty info.1244static DebugLoc getOrCreateDebugLoc(const Instruction *InsertBefore,1245DISubprogram *SP) {1246assert(InsertBefore);1247if (InsertBefore->getDebugLoc())1248return InsertBefore->getDebugLoc();1249const Instruction *Prev = InsertBefore->getPrevNode();1250if (Prev && Prev->getDebugLoc())1251return Prev->getDebugLoc();1252if (SP)1253return DILocation::get(SP->getContext(), SP->getLine(), 1, SP);1254return DebugLoc();1255}12561257bool WebAssemblyLowerEmscriptenEHSjLj::runSjLjOnFunction(Function &F) {1258assert(EnableEmSjLj || EnableWasmSjLj);1259Module &M = *F.getParent();1260LLVMContext &C = F.getContext();1261IRBuilder<> IRB(C);1262SmallVector<Instruction *, 64> ToErase;12631264// Setjmp preparation12651266BasicBlock *Entry = &F.getEntryBlock();1267DebugLoc FirstDL = getOrCreateDebugLoc(&*Entry->begin(), F.getSubprogram());1268SplitBlock(Entry, &*Entry->getFirstInsertionPt());12691270IRB.SetInsertPoint(Entry->getTerminator()->getIterator());1271// This alloca'ed pointer is used by the runtime to identify function1272// invocations. It's just for pointer comparisons. It will never be1273// dereferenced.1274Instruction *FunctionInvocationId =1275IRB.CreateAlloca(IRB.getInt32Ty(), nullptr, "functionInvocationId");1276FunctionInvocationId->setDebugLoc(FirstDL);12771278// Setjmp transformation1279SmallVector<PHINode *, 4> SetjmpRetPHIs;1280Function *SetjmpF = M.getFunction("setjmp");1281for (auto *U : make_early_inc_range(SetjmpF->users())) {1282auto *CB = cast<CallBase>(U);1283BasicBlock *BB = CB->getParent();1284if (BB->getParent() != &F) // in other function1285continue;1286if (CB->getOperandBundle(LLVMContext::OB_funclet)) {1287std::string S;1288raw_string_ostream SS(S);1289SS << "In function " + F.getName() +1290": setjmp within a catch clause is not supported in Wasm EH:\n";1291SS << *CB;1292report_fatal_error(StringRef(SS.str()));1293}12941295CallInst *CI = nullptr;1296// setjmp cannot throw. So if it is an invoke, lower it to a call1297if (auto *II = dyn_cast<InvokeInst>(CB))1298CI = llvm::changeToCall(II);1299else1300CI = cast<CallInst>(CB);13011302// The tail is everything right after the call, and will be reached once1303// when setjmp is called, and later when longjmp returns to the setjmp1304BasicBlock *Tail = SplitBlock(BB, CI->getNextNode());1305// Add a phi to the tail, which will be the output of setjmp, which1306// indicates if this is the first call or a longjmp back. The phi directly1307// uses the right value based on where we arrive from1308IRB.SetInsertPoint(Tail, Tail->getFirstNonPHIIt());1309PHINode *SetjmpRet = IRB.CreatePHI(IRB.getInt32Ty(), 2, "setjmp.ret");13101311// setjmp initial call returns 01312SetjmpRet->addIncoming(IRB.getInt32(0), BB);1313// The proper output is now this, not the setjmp call itself1314CI->replaceAllUsesWith(SetjmpRet);1315// longjmp returns to the setjmp will add themselves to this phi1316SetjmpRetPHIs.push_back(SetjmpRet);13171318// Fix call target1319// Our index in the function is our place in the array + 1 to avoid index1320// 0, because index 0 means the longjmp is not ours to handle.1321IRB.SetInsertPoint(CI);1322Value *Args[] = {CI->getArgOperand(0), IRB.getInt32(SetjmpRetPHIs.size()),1323FunctionInvocationId};1324IRB.CreateCall(WasmSetjmpF, Args);1325ToErase.push_back(CI);1326}13271328// Handle longjmpable calls.1329if (EnableEmSjLj)1330handleLongjmpableCallsForEmscriptenSjLj(F, FunctionInvocationId,1331SetjmpRetPHIs);1332else // EnableWasmSjLj1333handleLongjmpableCallsForWasmSjLj(F, FunctionInvocationId, SetjmpRetPHIs);13341335// Erase everything we no longer need in this function1336for (Instruction *I : ToErase)1337I->eraseFromParent();13381339// Finally, our modifications to the cfg can break dominance of SSA variables.1340// For example, in this code,1341// if (x()) { .. setjmp() .. }1342// if (y()) { .. longjmp() .. }1343// We must split the longjmp block, and it can jump into the block splitted1344// from setjmp one. But that means that when we split the setjmp block, it's1345// first part no longer dominates its second part - there is a theoretically1346// possible control flow path where x() is false, then y() is true and we1347// reach the second part of the setjmp block, without ever reaching the first1348// part. So, we rebuild SSA form here.1349rebuildSSA(F);1350return true;1351}13521353// Update each call that can longjmp so it can return to the corresponding1354// setjmp. Refer to 4) of "Emscripten setjmp/longjmp handling" section in the1355// comments at top of the file for details.1356void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForEmscriptenSjLj(1357Function &F, Instruction *FunctionInvocationId,1358SmallVectorImpl<PHINode *> &SetjmpRetPHIs) {1359Module &M = *F.getParent();1360LLVMContext &C = F.getContext();1361IRBuilder<> IRB(C);1362SmallVector<Instruction *, 64> ToErase;13631364// call.em.longjmp BB that will be shared within the function.1365BasicBlock *CallEmLongjmpBB = nullptr;1366// PHI node for the loaded value of __THREW__ global variable in1367// call.em.longjmp BB1368PHINode *CallEmLongjmpBBThrewPHI = nullptr;1369// PHI node for the loaded value of __threwValue global variable in1370// call.em.longjmp BB1371PHINode *CallEmLongjmpBBThrewValuePHI = nullptr;1372// rethrow.exn BB that will be shared within the function.1373BasicBlock *RethrowExnBB = nullptr;13741375// Because we are creating new BBs while processing and don't want to make1376// all these newly created BBs candidates again for longjmp processing, we1377// first make the vector of candidate BBs.1378std::vector<BasicBlock *> BBs;1379for (BasicBlock &BB : F)1380BBs.push_back(&BB);13811382// BBs.size() will change within the loop, so we query it every time1383for (unsigned I = 0; I < BBs.size(); I++) {1384BasicBlock *BB = BBs[I];1385for (Instruction &I : *BB) {1386if (isa<InvokeInst>(&I)) {1387std::string S;1388raw_string_ostream SS(S);1389SS << "In function " << F.getName()1390<< ": When using Wasm EH with Emscripten SjLj, there is a "1391"restriction that `setjmp` function call and exception cannot be "1392"used within the same function:\n";1393SS << I;1394report_fatal_error(StringRef(SS.str()));1395}1396auto *CI = dyn_cast<CallInst>(&I);1397if (!CI)1398continue;13991400const Value *Callee = CI->getCalledOperand();1401if (!canLongjmp(Callee))1402continue;1403if (isEmAsmCall(Callee))1404report_fatal_error("Cannot use EM_ASM* alongside setjmp/longjmp in " +1405F.getName() +1406". Please consider using EM_JS, or move the "1407"EM_ASM into another function.",1408false);14091410Value *Threw = nullptr;1411BasicBlock *Tail;1412if (Callee->getName().starts_with("__invoke_")) {1413// If invoke wrapper has already been generated for this call in1414// previous EH phase, search for the load instruction1415// %__THREW__.val = __THREW__;1416// in postamble after the invoke wrapper call1417LoadInst *ThrewLI = nullptr;1418StoreInst *ThrewResetSI = nullptr;1419for (auto I = std::next(BasicBlock::iterator(CI)), IE = BB->end();1420I != IE; ++I) {1421if (auto *LI = dyn_cast<LoadInst>(I))1422if (auto *GV = dyn_cast<GlobalVariable>(LI->getPointerOperand()))1423if (GV == ThrewGV) {1424Threw = ThrewLI = LI;1425break;1426}1427}1428// Search for the store instruction after the load above1429// __THREW__ = 0;1430for (auto I = std::next(BasicBlock::iterator(ThrewLI)), IE = BB->end();1431I != IE; ++I) {1432if (auto *SI = dyn_cast<StoreInst>(I)) {1433if (auto *GV = dyn_cast<GlobalVariable>(SI->getPointerOperand())) {1434if (GV == ThrewGV &&1435SI->getValueOperand() == getAddrSizeInt(&M, 0)) {1436ThrewResetSI = SI;1437break;1438}1439}1440}1441}1442assert(Threw && ThrewLI && "Cannot find __THREW__ load after invoke");1443assert(ThrewResetSI && "Cannot find __THREW__ store after invoke");1444Tail = SplitBlock(BB, ThrewResetSI->getNextNode());14451446} else {1447// Wrap call with invoke wrapper and generate preamble/postamble1448Threw = wrapInvoke(CI);1449ToErase.push_back(CI);1450Tail = SplitBlock(BB, CI->getNextNode());14511452// If exception handling is enabled, the thrown value can be not a1453// longjmp but an exception, in which case we shouldn't silently ignore1454// exceptions; we should rethrow them.1455// __THREW__'s value is 0 when nothing happened, 1 when an exception is1456// thrown, other values when longjmp is thrown.1457//1458// if (%__THREW__.val == 1)1459// goto %eh.rethrow1460// else1461// goto %normal1462//1463// eh.rethrow: ;; Rethrow exception1464// %exn = call @__cxa_find_matching_catch_2() ;; Retrieve thrown ptr1465// __resumeException(%exn)1466//1467// normal:1468// <-- Insertion point. Will insert sjlj handling code from here1469// goto %tail1470//1471// tail:1472// ...1473if (supportsException(&F) && canThrow(Callee)) {1474// We will add a new conditional branch. So remove the branch created1475// when we split the BB1476ToErase.push_back(BB->getTerminator());14771478// Generate rethrow.exn BB once and share it within the function1479if (!RethrowExnBB) {1480RethrowExnBB = BasicBlock::Create(C, "rethrow.exn", &F);1481IRB.SetInsertPoint(RethrowExnBB);1482CallInst *Exn =1483IRB.CreateCall(getFindMatchingCatch(M, 0), {}, "exn");1484IRB.CreateCall(ResumeF, {Exn});1485IRB.CreateUnreachable();1486}14871488IRB.SetInsertPoint(CI);1489BasicBlock *NormalBB = BasicBlock::Create(C, "normal", &F);1490Value *CmpEqOne =1491IRB.CreateICmpEQ(Threw, getAddrSizeInt(&M, 1), "cmp.eq.one");1492IRB.CreateCondBr(CmpEqOne, RethrowExnBB, NormalBB);14931494IRB.SetInsertPoint(NormalBB);1495IRB.CreateBr(Tail);1496BB = NormalBB; // New insertion point to insert __wasm_setjmp_test()1497}1498}14991500// We need to replace the terminator in Tail - SplitBlock makes BB go1501// straight to Tail, we need to check if a longjmp occurred, and go to the1502// right setjmp-tail if so1503ToErase.push_back(BB->getTerminator());15041505// Generate a function call to __wasm_setjmp_test function and1506// preamble/postamble code to figure out (1) whether longjmp1507// occurred (2) if longjmp occurred, which setjmp it corresponds to1508Value *Label = nullptr;1509Value *LongjmpResult = nullptr;1510BasicBlock *EndBB = nullptr;1511wrapTestSetjmp(BB, CI->getDebugLoc(), Threw, FunctionInvocationId, Label,1512LongjmpResult, CallEmLongjmpBB, CallEmLongjmpBBThrewPHI,1513CallEmLongjmpBBThrewValuePHI, EndBB);1514assert(Label && LongjmpResult && EndBB);15151516// Create switch instruction1517IRB.SetInsertPoint(EndBB);1518IRB.SetCurrentDebugLocation(EndBB->back().getDebugLoc());1519SwitchInst *SI = IRB.CreateSwitch(Label, Tail, SetjmpRetPHIs.size());1520// -1 means no longjmp happened, continue normally (will hit the default1521// switch case). 0 means a longjmp that is not ours to handle, needs a1522// rethrow. Otherwise the index is the same as the index in P+1 (to avoid1523// 0).1524for (unsigned I = 0; I < SetjmpRetPHIs.size(); I++) {1525SI->addCase(IRB.getInt32(I + 1), SetjmpRetPHIs[I]->getParent());1526SetjmpRetPHIs[I]->addIncoming(LongjmpResult, EndBB);1527}15281529// We are splitting the block here, and must continue to find other calls1530// in the block - which is now split. so continue to traverse in the Tail1531BBs.push_back(Tail);1532}1533}15341535for (Instruction *I : ToErase)1536I->eraseFromParent();1537}15381539static BasicBlock *getCleanupRetUnwindDest(const CleanupPadInst *CPI) {1540for (const User *U : CPI->users())1541if (const auto *CRI = dyn_cast<CleanupReturnInst>(U))1542return CRI->getUnwindDest();1543return nullptr;1544}15451546// Create a catchpad in which we catch a longjmp's env and val arguments, test1547// if the longjmp corresponds to one of setjmps in the current function, and if1548// so, jump to the setjmp dispatch BB from which we go to one of post-setjmp1549// BBs. Refer to 4) of "Wasm setjmp/longjmp handling" section in the comments at1550// top of the file for details.1551void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(1552Function &F, Instruction *FunctionInvocationId,1553SmallVectorImpl<PHINode *> &SetjmpRetPHIs) {1554Module &M = *F.getParent();1555LLVMContext &C = F.getContext();1556IRBuilder<> IRB(C);15571558// A function with catchswitch/catchpad instruction should have a personality1559// function attached to it. Search for the wasm personality function, and if1560// it exists, use it, and if it doesn't, create a dummy personality function.1561// (SjLj is not going to call it anyway.)1562if (!F.hasPersonalityFn()) {1563StringRef PersName = getEHPersonalityName(EHPersonality::Wasm_CXX);1564FunctionType *PersType =1565FunctionType::get(IRB.getInt32Ty(), /* isVarArg */ true);1566Value *PersF = M.getOrInsertFunction(PersName, PersType).getCallee();1567F.setPersonalityFn(1568cast<Constant>(IRB.CreateBitCast(PersF, IRB.getPtrTy())));1569}15701571// Use the entry BB's debugloc as a fallback1572BasicBlock *Entry = &F.getEntryBlock();1573DebugLoc FirstDL = getOrCreateDebugLoc(&*Entry->begin(), F.getSubprogram());1574IRB.SetCurrentDebugLocation(FirstDL);15751576// Add setjmp.dispatch BB right after the entry block. Because we have1577// initialized functionInvocationId in the entry block and split the1578// rest into another BB, here 'OrigEntry' is the function's original entry1579// block before the transformation.1580//1581// entry:1582// functionInvocationId initialization1583// setjmp.dispatch:1584// switch will be inserted here later1585// entry.split: (OrigEntry)1586// the original function starts here1587BasicBlock *OrigEntry = Entry->getNextNode();1588BasicBlock *SetjmpDispatchBB =1589BasicBlock::Create(C, "setjmp.dispatch", &F, OrigEntry);1590cast<BranchInst>(Entry->getTerminator())->setSuccessor(0, SetjmpDispatchBB);15911592// Create catch.dispatch.longjmp BB and a catchswitch instruction1593BasicBlock *CatchDispatchLongjmpBB =1594BasicBlock::Create(C, "catch.dispatch.longjmp", &F);1595IRB.SetInsertPoint(CatchDispatchLongjmpBB);1596CatchSwitchInst *CatchSwitchLongjmp =1597IRB.CreateCatchSwitch(ConstantTokenNone::get(C), nullptr, 1);15981599// Create catch.longjmp BB and a catchpad instruction1600BasicBlock *CatchLongjmpBB = BasicBlock::Create(C, "catch.longjmp", &F);1601CatchSwitchLongjmp->addHandler(CatchLongjmpBB);1602IRB.SetInsertPoint(CatchLongjmpBB);1603CatchPadInst *CatchPad = IRB.CreateCatchPad(CatchSwitchLongjmp, {});16041605// Wasm throw and catch instructions can throw and catch multiple values, but1606// that requires multivalue support in the toolchain, which is currently not1607// very reliable. We instead throw and catch a pointer to a struct value of1608// type 'struct __WasmLongjmpArgs', which is defined in Emscripten.1609Instruction *LongjmpArgs =1610IRB.CreateCall(CatchF, {IRB.getInt32(WebAssembly::C_LONGJMP)}, "thrown");1611Value *EnvField =1612IRB.CreateConstGEP2_32(LongjmpArgsTy, LongjmpArgs, 0, 0, "env_gep");1613Value *ValField =1614IRB.CreateConstGEP2_32(LongjmpArgsTy, LongjmpArgs, 0, 1, "val_gep");1615// void *env = __wasm_longjmp_args.env;1616Instruction *Env = IRB.CreateLoad(IRB.getPtrTy(), EnvField, "env");1617// int val = __wasm_longjmp_args.val;1618Instruction *Val = IRB.CreateLoad(IRB.getInt32Ty(), ValField, "val");16191620// %label = __wasm_setjmp_test(%env, functionInvocatinoId);1621// if (%label == 0)1622// __wasm_longjmp(%env, %val)1623// catchret to %setjmp.dispatch1624BasicBlock *ThenBB = BasicBlock::Create(C, "if.then", &F);1625BasicBlock *EndBB = BasicBlock::Create(C, "if.end", &F);1626Value *EnvP = IRB.CreateBitCast(Env, getAddrPtrType(&M), "env.p");1627Value *Label = IRB.CreateCall(WasmSetjmpTestF, {EnvP, FunctionInvocationId},1628OperandBundleDef("funclet", CatchPad), "label");1629Value *Cmp = IRB.CreateICmpEQ(Label, IRB.getInt32(0));1630IRB.CreateCondBr(Cmp, ThenBB, EndBB);16311632IRB.SetInsertPoint(ThenBB);1633CallInst *WasmLongjmpCI = IRB.CreateCall(1634WasmLongjmpF, {Env, Val}, OperandBundleDef("funclet", CatchPad));1635IRB.CreateUnreachable();16361637IRB.SetInsertPoint(EndBB);1638// Jump to setjmp.dispatch block1639IRB.CreateCatchRet(CatchPad, SetjmpDispatchBB);16401641// Go back to setjmp.dispatch BB1642// setjmp.dispatch:1643// switch %label {1644// label 1: goto post-setjmp BB 11645// label 2: goto post-setjmp BB 21646// ...1647// default: goto splitted next BB1648// }1649IRB.SetInsertPoint(SetjmpDispatchBB);1650PHINode *LabelPHI = IRB.CreatePHI(IRB.getInt32Ty(), 2, "label.phi");1651LabelPHI->addIncoming(Label, EndBB);1652LabelPHI->addIncoming(IRB.getInt32(-1), Entry);1653SwitchInst *SI = IRB.CreateSwitch(LabelPHI, OrigEntry, SetjmpRetPHIs.size());1654// -1 means no longjmp happened, continue normally (will hit the default1655// switch case). 0 means a longjmp that is not ours to handle, needs a1656// rethrow. Otherwise the index is the same as the index in P+1 (to avoid1657// 0).1658for (unsigned I = 0; I < SetjmpRetPHIs.size(); I++) {1659SI->addCase(IRB.getInt32(I + 1), SetjmpRetPHIs[I]->getParent());1660SetjmpRetPHIs[I]->addIncoming(Val, SetjmpDispatchBB);1661}16621663// Convert all longjmpable call instructions to invokes that unwind to the1664// newly created catch.dispatch.longjmp BB.1665SmallVector<CallInst *, 64> LongjmpableCalls;1666for (auto *BB = &*F.begin(); BB; BB = BB->getNextNode()) {1667for (auto &I : *BB) {1668auto *CI = dyn_cast<CallInst>(&I);1669if (!CI)1670continue;1671const Value *Callee = CI->getCalledOperand();1672if (!canLongjmp(Callee))1673continue;1674if (isEmAsmCall(Callee))1675report_fatal_error("Cannot use EM_ASM* alongside setjmp/longjmp in " +1676F.getName() +1677". Please consider using EM_JS, or move the "1678"EM_ASM into another function.",1679false);1680// This is __wasm_longjmp() call we inserted in this function, which1681// rethrows the longjmp when the longjmp does not correspond to one of1682// setjmps in this function. We should not convert this call to an invoke.1683if (CI == WasmLongjmpCI)1684continue;1685LongjmpableCalls.push_back(CI);1686}1687}16881689for (auto *CI : LongjmpableCalls) {1690// Even if the callee function has attribute 'nounwind', which is true for1691// all C functions, it can longjmp, which means it can throw a Wasm1692// exception now.1693CI->removeFnAttr(Attribute::NoUnwind);1694if (Function *CalleeF = CI->getCalledFunction())1695CalleeF->removeFnAttr(Attribute::NoUnwind);16961697// Change it to an invoke and make it unwind to the catch.dispatch.longjmp1698// BB. If the call is enclosed in another catchpad/cleanuppad scope, unwind1699// to its parent pad's unwind destination instead to preserve the scope1700// structure. It will eventually unwind to the catch.dispatch.longjmp.1701SmallVector<OperandBundleDef, 1> Bundles;1702BasicBlock *UnwindDest = nullptr;1703if (auto Bundle = CI->getOperandBundle(LLVMContext::OB_funclet)) {1704Instruction *FromPad = cast<Instruction>(Bundle->Inputs[0]);1705while (!UnwindDest) {1706if (auto *CPI = dyn_cast<CatchPadInst>(FromPad)) {1707UnwindDest = CPI->getCatchSwitch()->getUnwindDest();1708break;1709}1710if (auto *CPI = dyn_cast<CleanupPadInst>(FromPad)) {1711// getCleanupRetUnwindDest() can return nullptr when1712// 1. This cleanuppad's matching cleanupret uwninds to caller1713// 2. There is no matching cleanupret because it ends with1714// unreachable.1715// In case of 2, we need to traverse the parent pad chain.1716UnwindDest = getCleanupRetUnwindDest(CPI);1717Value *ParentPad = CPI->getParentPad();1718if (isa<ConstantTokenNone>(ParentPad))1719break;1720FromPad = cast<Instruction>(ParentPad);1721}1722}1723}1724if (!UnwindDest)1725UnwindDest = CatchDispatchLongjmpBB;1726changeToInvokeAndSplitBasicBlock(CI, UnwindDest);1727}17281729SmallVector<Instruction *, 16> ToErase;1730for (auto &BB : F) {1731if (auto *CSI = dyn_cast<CatchSwitchInst>(BB.getFirstNonPHI())) {1732if (CSI != CatchSwitchLongjmp && CSI->unwindsToCaller()) {1733IRB.SetInsertPoint(CSI);1734ToErase.push_back(CSI);1735auto *NewCSI = IRB.CreateCatchSwitch(CSI->getParentPad(),1736CatchDispatchLongjmpBB, 1);1737NewCSI->addHandler(*CSI->handler_begin());1738NewCSI->takeName(CSI);1739CSI->replaceAllUsesWith(NewCSI);1740}1741}17421743if (auto *CRI = dyn_cast<CleanupReturnInst>(BB.getTerminator())) {1744if (CRI->unwindsToCaller()) {1745IRB.SetInsertPoint(CRI);1746ToErase.push_back(CRI);1747IRB.CreateCleanupRet(CRI->getCleanupPad(), CatchDispatchLongjmpBB);1748}1749}1750}17511752for (Instruction *I : ToErase)1753I->eraseFromParent();1754}175517561757