123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878 |
- //=== WebAssemblyLowerEmscriptenEHSjLj.cpp - Lower exceptions for Emscripten =//
- //
- // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
- // See https://llvm.org/LICENSE.txt for license information.
- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- //
- //===----------------------------------------------------------------------===//
- ///
- /// \file
- /// This file lowers exception-related instructions and setjmp/longjmp function
- /// calls to use Emscripten's library functions. The pass uses JavaScript's try
- /// and catch mechanism in case of Emscripten EH/SjLj and Wasm EH intrinsics in
- /// case of Emscripten SjLJ.
- ///
- /// * Emscripten exception handling
- /// This pass lowers invokes and landingpads into library functions in JS glue
- /// code. Invokes are lowered into function wrappers called invoke wrappers that
- /// exist in JS side, which wraps the original function call with JS try-catch.
- /// If an exception occurred, cxa_throw() function in JS side sets some
- /// variables (see below) so we can check whether an exception occurred from
- /// wasm code and handle it appropriately.
- ///
- /// * Emscripten setjmp-longjmp handling
- /// This pass lowers setjmp to a reasonably-performant approach for emscripten.
- /// The idea is that each block with a setjmp is broken up into two parts: the
- /// part containing setjmp and the part right after the setjmp. The latter part
- /// is either reached from the setjmp, or later from a longjmp. To handle the
- /// longjmp, all calls that might longjmp are also called using invoke wrappers
- /// and thus JS / try-catch. JS longjmp() function also sets some variables so
- /// we can check / whether a longjmp occurred from wasm code. Each block with a
- /// function call that might longjmp is also split up after the longjmp call.
- /// After the longjmp call, we check whether a longjmp occurred, and if it did,
- /// which setjmp it corresponds to, and jump to the right post-setjmp block.
- /// We assume setjmp-longjmp handling always run after EH handling, which means
- /// we don't expect any exception-related instructions when SjLj runs.
- /// FIXME Currently this scheme does not support indirect call of setjmp,
- /// because of the limitation of the scheme itself. fastcomp does not support it
- /// either.
- ///
- /// In detail, this pass does following things:
- ///
- /// 1) Assumes the existence of global variables: __THREW__, __threwValue
- /// __THREW__ and __threwValue are defined in compiler-rt in Emscripten.
- /// These variables are used for both exceptions and setjmp/longjmps.
- /// __THREW__ indicates whether an exception or a longjmp occurred or not. 0
- /// means nothing occurred, 1 means an exception occurred, and other numbers
- /// mean a longjmp occurred. In the case of longjmp, __THREW__ variable
- /// indicates the corresponding setjmp buffer the longjmp corresponds to.
- /// __threwValue is 0 for exceptions, and the argument to longjmp in case of
- /// longjmp.
- ///
- /// * Emscripten exception handling
- ///
- /// 2) We assume the existence of setThrew and setTempRet0/getTempRet0 functions
- /// at link time. setThrew exists in Emscripten's compiler-rt:
- ///
- /// void setThrew(uintptr_t threw, int value) {
- /// if (__THREW__ == 0) {
- /// __THREW__ = threw;
- /// __threwValue = value;
- /// }
- /// }
- //
- /// setTempRet0 is called from __cxa_find_matching_catch() in JS glue code.
- /// In exception handling, getTempRet0 indicates the type of an exception
- /// caught, and in setjmp/longjmp, it means the second argument to longjmp
- /// function.
- ///
- /// 3) Lower
- /// invoke @func(arg1, arg2) to label %invoke.cont unwind label %lpad
- /// into
- /// __THREW__ = 0;
- /// call @__invoke_SIG(func, arg1, arg2)
- /// %__THREW__.val = __THREW__;
- /// __THREW__ = 0;
- /// if (%__THREW__.val == 1)
- /// goto %lpad
- /// else
- /// goto %invoke.cont
- /// SIG is a mangled string generated based on the LLVM IR-level function
- /// signature. After LLVM IR types are lowered to the target wasm types,
- /// the names for these wrappers will change based on wasm types as well,
- /// as in invoke_vi (function takes an int and returns void). The bodies of
- /// these wrappers will be generated in JS glue code, and inside those
- /// wrappers we use JS try-catch to generate actual exception effects. It
- /// also calls the original callee function. An example wrapper in JS code
- /// would look like this:
- /// function invoke_vi(index,a1) {
- /// try {
- /// Module["dynCall_vi"](index,a1); // This calls original callee
- /// } catch(e) {
- /// if (typeof e !== 'number' && e !== 'longjmp') throw e;
- /// _setThrew(1, 0); // setThrew is called here
- /// }
- /// }
- /// If an exception is thrown, __THREW__ will be set to true in a wrapper,
- /// so we can jump to the right BB based on this value.
- ///
- /// 4) Lower
- /// %val = landingpad catch c1 catch c2 catch c3 ...
- /// ... use %val ...
- /// into
- /// %fmc = call @__cxa_find_matching_catch_N(c1, c2, c3, ...)
- /// %val = {%fmc, getTempRet0()}
- /// ... use %val ...
- /// Here N is a number calculated based on the number of clauses.
- /// setTempRet0 is called from __cxa_find_matching_catch() in JS glue code.
- ///
- /// 5) Lower
- /// resume {%a, %b}
- /// into
- /// call @__resumeException(%a)
- /// where __resumeException() is a function in JS glue code.
- ///
- /// 6) Lower
- /// call @llvm.eh.typeid.for(type) (intrinsic)
- /// into
- /// call @llvm_eh_typeid_for(type)
- /// llvm_eh_typeid_for function will be generated in JS glue code.
- ///
- /// * Emscripten setjmp / longjmp handling
- ///
- /// If there are calls to longjmp()
- ///
- /// 1) Lower
- /// longjmp(env, val)
- /// into
- /// emscripten_longjmp(env, val)
- ///
- /// If there are calls to setjmp()
- ///
- /// 2) 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 to call saveSetjmp() function in
- /// Emscripten compiler-rt.
- ///
- /// 3) Lower
- /// setjmp(env)
- /// into
- /// setjmpTable = saveSetjmp(env, label, setjmpTable, setjmpTableSize);
- /// setjmpTableSize = getTempRet0();
- /// 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 Emscripten's
- /// compiler-rt and it will return the new table address, and assign the new
- /// table size in setTempRet0(). saveSetjmp also stores the setjmp's ID into
- /// the buffer 'env'. A BB with setjmp is split into two after setjmp call in
- /// order to make the post-setjmp BB the possible destination of longjmp BB.
- ///
- /// 4) Lower every call that might longjmp into
- /// __THREW__ = 0;
- /// call @__invoke_SIG(func, arg1, arg2)
- /// %__THREW__.val = __THREW__;
- /// __THREW__ = 0;
- /// %__threwValue.val = __threwValue;
- /// if (%__THREW__.val != 0 & %__threwValue.val != 0) {
- /// %label = testSetjmp(mem[%__THREW__.val], setjmpTable,
- /// setjmpTableSize);
- /// if (%label == 0)
- /// emscripten_longjmp(%__THREW__.val, %__threwValue.val);
- /// setTempRet0(%__threwValue.val);
- /// } else {
- /// %label = -1;
- /// }
- /// longjmp_result = getTempRet0();
- /// switch %label {
- /// label 1: goto post-setjmp BB 1
- /// label 2: goto post-setjmp BB 2
- /// ...
- /// default: goto splitted next BB
- /// }
- /// testSetjmp examines setjmpTable to see if there is a matching setjmp
- /// call. After calling an invoke wrapper, if a longjmp occurred, __THREW__
- /// will be the address of matching jmp_buf buffer and __threwValue be the
- /// second argument to longjmp. mem[%__THREW__.val] is a setjmp ID that is
- /// stored in saveSetjmp. testSetjmp returns a setjmp label, a unique ID to
- /// each setjmp callsite. Label 0 means this longjmp buffer does not
- /// correspond to one of the setjmp callsites in this function, so in this
- /// case we just chain the longjmp to the caller. Label -1 means no longjmp
- /// occurred. Otherwise we jump to the right post-setjmp BB based on the
- /// label.
- ///
- /// * Wasm setjmp / longjmp handling
- /// This mode still uses some Emscripten library functions but not JavaScript's
- /// try-catch mechanism. It instead uses Wasm exception handling intrinsics,
- /// which will be lowered to exception handling instructions.
- ///
- /// If there are calls to longjmp()
- ///
- /// 1) Lower
- /// longjmp(env, val)
- /// into
- /// __wasm_longjmp(env, val)
- ///
- /// If there are calls to setjmp()
- ///
- /// 2) and 3): The same as 2) and 3) in Emscripten SjLj.
- /// (setjmpTable/setjmpTableSize initialization + setjmp callsite
- /// transformation)
- ///
- /// 4) Create a catchpad with a wasm.catch() intrinsic, which returns the value
- /// thrown by __wasm_longjmp function. In Emscripten library, we have this
- /// struct:
- ///
- /// struct __WasmLongjmpArgs {
- /// void *env;
- /// int val;
- /// };
- /// struct __WasmLongjmpArgs __wasm_longjmp_args;
- ///
- /// The thrown value here is a pointer to __wasm_longjmp_args struct object. We
- /// use this struct to transfer two values by throwing a single value. Wasm
- /// throw and catch instructions are capable of throwing and catching multiple
- /// values, but it also requires multivalue support that is currently not very
- /// reliable.
- /// TODO Switch to throwing and catching two values without using the struct
- ///
- /// All longjmpable function calls will be converted to an invoke that will
- /// unwind to this catchpad in case a longjmp occurs. Within the catchpad, we
- /// test the thrown values using testSetjmp function as we do for Emscripten
- /// SjLj. The main difference is, in Emscripten SjLj, we need to transform every
- /// longjmpable callsite into a sequence of code including testSetjmp() call; in
- /// Wasm SjLj we do the testing in only one place, in this catchpad.
- ///
- /// After testing calling testSetjmp(), if the longjmp does not correspond to
- /// one of the setjmps within the current function, it rethrows the longjmp
- /// by calling __wasm_longjmp(). If it corresponds to one of setjmps in the
- /// function, we jump to the beginning of the function, which contains a switch
- /// to each post-setjmp BB. Again, in Emscripten SjLj, this switch is added for
- /// every longjmpable callsite; in Wasm SjLj we do this only once at the top of
- /// the function. (after setjmpTable/setjmpTableSize initialization)
- ///
- /// The below is the pseudocode for what we have described
- ///
- /// entry:
- /// Initialize setjmpTable and setjmpTableSize
- ///
- /// setjmp.dispatch:
- /// switch %label {
- /// label 1: goto post-setjmp BB 1
- /// label 2: goto post-setjmp BB 2
- /// ...
- /// default: goto splitted next BB
- /// }
- /// ...
- ///
- /// bb:
- /// invoke void @foo() ;; foo is a longjmpable function
- /// to label %next unwind label %catch.dispatch.longjmp
- /// ...
- ///
- /// catch.dispatch.longjmp:
- /// %0 = catchswitch within none [label %catch.longjmp] unwind to caller
- ///
- /// catch.longjmp:
- /// %longjmp.args = wasm.catch() ;; struct __WasmLongjmpArgs
- /// %env = load 'env' field from __WasmLongjmpArgs
- /// %val = load 'val' field from __WasmLongjmpArgs
- /// %label = testSetjmp(mem[%env], setjmpTable, setjmpTableSize);
- /// if (%label == 0)
- /// __wasm_longjmp(%env, %val)
- /// catchret to %setjmp.dispatch
- ///
- ///===----------------------------------------------------------------------===//
- #include "Utils/WebAssemblyUtilities.h"
- #include "WebAssembly.h"
- #include "WebAssemblyTargetMachine.h"
- #include "llvm/ADT/StringExtras.h"
- #include "llvm/CodeGen/TargetPassConfig.h"
- #include "llvm/CodeGen/WasmEHFuncInfo.h"
- #include "llvm/IR/DebugInfoMetadata.h"
- #include "llvm/IR/Dominators.h"
- #include "llvm/IR/IRBuilder.h"
- #include "llvm/IR/IntrinsicsWebAssembly.h"
- #include "llvm/Support/CommandLine.h"
- #include "llvm/Transforms/Utils/BasicBlockUtils.h"
- #include "llvm/Transforms/Utils/Local.h"
- #include "llvm/Transforms/Utils/SSAUpdater.h"
- #include "llvm/Transforms/Utils/SSAUpdaterBulk.h"
- using namespace llvm;
- #define DEBUG_TYPE "wasm-lower-em-ehsjlj"
- static cl::list<std::string>
- EHAllowlist("emscripten-cxx-exceptions-allowed",
- cl::desc("The list of function names in which Emscripten-style "
- "exception handling is enabled (see emscripten "
- "EMSCRIPTEN_CATCHING_ALLOWED options)"),
- cl::CommaSeparated);
- namespace {
- class WebAssemblyLowerEmscriptenEHSjLj final : public ModulePass {
- bool EnableEmEH; // Enable Emscripten exception handling
- bool EnableEmSjLj; // Enable Emscripten setjmp/longjmp handling
- bool EnableWasmSjLj; // Enable Wasm setjmp/longjmp handling
- bool DoSjLj; // Whether we actually perform setjmp/longjmp handling
- GlobalVariable *ThrewGV = nullptr; // __THREW__ (Emscripten)
- GlobalVariable *ThrewValueGV = nullptr; // __threwValue (Emscripten)
- Function *GetTempRet0F = nullptr; // getTempRet0() (Emscripten)
- Function *SetTempRet0F = nullptr; // setTempRet0() (Emscripten)
- Function *ResumeF = nullptr; // __resumeException() (Emscripten)
- Function *EHTypeIDF = nullptr; // llvm.eh.typeid.for() (intrinsic)
- Function *EmLongjmpF = nullptr; // emscripten_longjmp() (Emscripten)
- Function *SaveSetjmpF = nullptr; // saveSetjmp() (Emscripten)
- Function *TestSetjmpF = nullptr; // testSetjmp() (Emscripten)
- Function *WasmLongjmpF = nullptr; // __wasm_longjmp() (Emscripten)
- Function *CatchF = nullptr; // wasm.catch() (intrinsic)
- // type of 'struct __WasmLongjmpArgs' defined in emscripten
- Type *LongjmpArgsTy = nullptr;
- // __cxa_find_matching_catch_N functions.
- // Indexed by the number of clauses in an original landingpad instruction.
- DenseMap<int, Function *> FindMatchingCatches;
- // Map of <function signature string, invoke_ wrappers>
- StringMap<Function *> InvokeWrappers;
- // Set of allowed function names for exception handling
- std::set<std::string> EHAllowlistSet;
- // Functions that contains calls to setjmp
- SmallPtrSet<Function *, 8> SetjmpUsers;
- StringRef getPassName() const override {
- return "WebAssembly Lower Emscripten Exceptions";
- }
- using InstVector = SmallVectorImpl<Instruction *>;
- bool runEHOnFunction(Function &F);
- bool runSjLjOnFunction(Function &F);
- void handleLongjmpableCallsForEmscriptenSjLj(
- Function &F, InstVector &SetjmpTableInsts,
- InstVector &SetjmpTableSizeInsts,
- SmallVectorImpl<PHINode *> &SetjmpRetPHIs);
- void
- handleLongjmpableCallsForWasmSjLj(Function &F, InstVector &SetjmpTableInsts,
- InstVector &SetjmpTableSizeInsts,
- SmallVectorImpl<PHINode *> &SetjmpRetPHIs);
- Function *getFindMatchingCatch(Module &M, unsigned NumClauses);
- Value *wrapInvoke(CallBase *CI);
- void wrapTestSetjmp(BasicBlock *BB, DebugLoc DL, Value *Threw,
- Value *SetjmpTable, Value *SetjmpTableSize, Value *&Label,
- Value *&LongjmpResult, BasicBlock *&CallEmLongjmpBB,
- PHINode *&CallEmLongjmpBBThrewPHI,
- PHINode *&CallEmLongjmpBBThrewValuePHI,
- BasicBlock *&EndBB);
- Function *getInvokeWrapper(CallBase *CI);
- bool areAllExceptionsAllowed() const { return EHAllowlistSet.empty(); }
- bool supportsException(const Function *F) const {
- return EnableEmEH && (areAllExceptionsAllowed() ||
- EHAllowlistSet.count(std::string(F->getName())));
- }
- void replaceLongjmpWith(Function *LongjmpF, Function *NewF);
- void rebuildSSA(Function &F);
- public:
- static char ID;
- WebAssemblyLowerEmscriptenEHSjLj()
- : ModulePass(ID), EnableEmEH(WebAssembly::WasmEnableEmEH),
- EnableEmSjLj(WebAssembly::WasmEnableEmSjLj),
- EnableWasmSjLj(WebAssembly::WasmEnableSjLj) {
- assert(!(EnableEmSjLj && EnableWasmSjLj) &&
- "Two SjLj modes cannot be turned on at the same time");
- assert(!(EnableEmEH && EnableWasmSjLj) &&
- "Wasm SjLj should be only used with Wasm EH");
- EHAllowlistSet.insert(EHAllowlist.begin(), EHAllowlist.end());
- }
- bool runOnModule(Module &M) override;
- void getAnalysisUsage(AnalysisUsage &AU) const override {
- AU.addRequired<DominatorTreeWrapperPass>();
- }
- };
- } // End anonymous namespace
- char WebAssemblyLowerEmscriptenEHSjLj::ID = 0;
- INITIALIZE_PASS(WebAssemblyLowerEmscriptenEHSjLj, DEBUG_TYPE,
- "WebAssembly Lower Emscripten Exceptions / Setjmp / Longjmp",
- false, false)
- ModulePass *llvm::createWebAssemblyLowerEmscriptenEHSjLj() {
- return new WebAssemblyLowerEmscriptenEHSjLj();
- }
- static bool canThrow(const Value *V) {
- if (const auto *F = dyn_cast<const Function>(V)) {
- // Intrinsics cannot throw
- if (F->isIntrinsic())
- return false;
- StringRef Name = F->getName();
- // leave setjmp and longjmp (mostly) alone, we process them properly later
- if (Name == "setjmp" || Name == "longjmp" || Name == "emscripten_longjmp")
- return false;
- return !F->doesNotThrow();
- }
- // not a function, so an indirect call - can throw, we can't tell
- return true;
- }
- // Get a thread-local global variable with the given name. If it doesn't exist
- // declare it, which will generate an import and assume that it will exist at
- // link time.
- static GlobalVariable *getGlobalVariable(Module &M, Type *Ty,
- WebAssemblyTargetMachine &TM,
- const char *Name) {
- auto *GV = dyn_cast<GlobalVariable>(M.getOrInsertGlobal(Name, Ty));
- if (!GV)
- report_fatal_error(Twine("unable to create global: ") + Name);
- // Variables created by this function are thread local. If the target does not
- // support TLS, we depend on CoalesceFeaturesAndStripAtomics to downgrade it
- // to non-thread-local ones, in which case we don't allow this object to be
- // linked with other objects using shared memory.
- GV->setThreadLocalMode(GlobalValue::GeneralDynamicTLSModel);
- return GV;
- }
- // Simple function name mangler.
- // This function simply takes LLVM's string representation of parameter types
- // and concatenate them with '_'. There are non-alphanumeric characters but llc
- // is ok with it, and we need to postprocess these names after the lowering
- // phase anyway.
- static std::string getSignature(FunctionType *FTy) {
- std::string Sig;
- raw_string_ostream OS(Sig);
- OS << *FTy->getReturnType();
- for (Type *ParamTy : FTy->params())
- OS << "_" << *ParamTy;
- if (FTy->isVarArg())
- OS << "_...";
- Sig = OS.str();
- erase_if(Sig, isSpace);
- // When s2wasm parses .s file, a comma means the end of an argument. So a
- // mangled function name can contain any character but a comma.
- std::replace(Sig.begin(), Sig.end(), ',', '.');
- return Sig;
- }
- static Function *getEmscriptenFunction(FunctionType *Ty, const Twine &Name,
- Module *M) {
- Function* F = Function::Create(Ty, GlobalValue::ExternalLinkage, Name, M);
- // Tell the linker that this function is expected to be imported from the
- // 'env' module.
- if (!F->hasFnAttribute("wasm-import-module")) {
- llvm::AttrBuilder B(M->getContext());
- B.addAttribute("wasm-import-module", "env");
- F->addFnAttrs(B);
- }
- if (!F->hasFnAttribute("wasm-import-name")) {
- llvm::AttrBuilder B(M->getContext());
- B.addAttribute("wasm-import-name", F->getName());
- F->addFnAttrs(B);
- }
- return F;
- }
- // Returns an integer type for the target architecture's address space.
- // i32 for wasm32 and i64 for wasm64.
- static Type *getAddrIntType(Module *M) {
- IRBuilder<> IRB(M->getContext());
- return IRB.getIntNTy(M->getDataLayout().getPointerSizeInBits());
- }
- // Returns an integer pointer type for the target architecture's address space.
- // i32* for wasm32 and i64* for wasm64.
- static Type *getAddrPtrType(Module *M) {
- return Type::getIntNPtrTy(M->getContext(),
- M->getDataLayout().getPointerSizeInBits());
- }
- // Returns an integer whose type is the integer type for the target's address
- // space. Returns (i32 C) for wasm32 and (i64 C) for wasm64, when C is the
- // integer.
- static Value *getAddrSizeInt(Module *M, uint64_t C) {
- IRBuilder<> IRB(M->getContext());
- return IRB.getIntN(M->getDataLayout().getPointerSizeInBits(), C);
- }
- // 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) {
- if (FindMatchingCatches.count(NumClauses))
- return FindMatchingCatches[NumClauses];
- PointerType *Int8PtrTy = Type::getInt8PtrTy(M.getContext());
- SmallVector<Type *, 16> Args(NumClauses, Int8PtrTy);
- FunctionType *FTy = FunctionType::get(Int8PtrTy, Args, false);
- Function *F = getEmscriptenFunction(
- FTy, "__cxa_find_matching_catch_" + Twine(NumClauses + 2), &M);
- FindMatchingCatches[NumClauses] = F;
- return F;
- }
- // Generate invoke wrapper seqence with preamble and postamble
- // Preamble:
- // __THREW__ = 0;
- // Postamble:
- // %__THREW__.val = __THREW__; __THREW__ = 0;
- // Returns %__THREW__.val, which indicates whether an exception is thrown (or
- // whether longjmp occurred), for future use.
- Value *WebAssemblyLowerEmscriptenEHSjLj::wrapInvoke(CallBase *CI) {
- Module *M = CI->getModule();
- LLVMContext &C = M->getContext();
- IRBuilder<> IRB(C);
- IRB.SetInsertPoint(CI);
- // Pre-invoke
- // __THREW__ = 0;
- IRB.CreateStore(getAddrSizeInt(M, 0), ThrewGV);
- // Invoke function wrapper in JavaScript
- SmallVector<Value *, 16> Args;
- // Put the pointer to the callee as first argument, so it can be called
- // within the invoke wrapper later
- Args.push_back(CI->getCalledOperand());
- Args.append(CI->arg_begin(), CI->arg_end());
- CallInst *NewCall = IRB.CreateCall(getInvokeWrapper(CI), Args);
- NewCall->takeName(CI);
- NewCall->setCallingConv(CallingConv::WASM_EmscriptenInvoke);
- NewCall->setDebugLoc(CI->getDebugLoc());
- // Because we added the pointer to the callee as first argument, all
- // argument attribute indices have to be incremented by one.
- SmallVector<AttributeSet, 8> ArgAttributes;
- const AttributeList &InvokeAL = CI->getAttributes();
- // No attributes for the callee pointer.
- ArgAttributes.push_back(AttributeSet());
- // Copy the argument attributes from the original
- for (unsigned I = 0, E = CI->arg_size(); I < E; ++I)
- ArgAttributes.push_back(InvokeAL.getParamAttrs(I));
- AttrBuilder FnAttrs(CI->getContext(), InvokeAL.getFnAttrs());
- if (auto Args = FnAttrs.getAllocSizeArgs()) {
- // The allocsize attribute (if any) referes to parameters by index and needs
- // to be adjusted.
- auto [SizeArg, NEltArg] = *Args;
- SizeArg += 1;
- if (NEltArg)
- NEltArg = *NEltArg + 1;
- FnAttrs.addAllocSizeAttr(SizeArg, NEltArg);
- }
- // In case the callee has 'noreturn' attribute, We need to remove it, because
- // we expect invoke wrappers to return.
- FnAttrs.removeAttribute(Attribute::NoReturn);
- // Reconstruct the AttributesList based on the vector we constructed.
- AttributeList NewCallAL = AttributeList::get(
- C, AttributeSet::get(C, FnAttrs), InvokeAL.getRetAttrs(), ArgAttributes);
- NewCall->setAttributes(NewCallAL);
- CI->replaceAllUsesWith(NewCall);
- // Post-invoke
- // %__THREW__.val = __THREW__; __THREW__ = 0;
- Value *Threw =
- IRB.CreateLoad(getAddrIntType(M), ThrewGV, ThrewGV->getName() + ".val");
- IRB.CreateStore(getAddrSizeInt(M, 0), ThrewGV);
- return Threw;
- }
- // Get matching invoke wrapper based on callee signature
- Function *WebAssemblyLowerEmscriptenEHSjLj::getInvokeWrapper(CallBase *CI) {
- Module *M = CI->getModule();
- SmallVector<Type *, 16> ArgTys;
- FunctionType *CalleeFTy = CI->getFunctionType();
- std::string Sig = getSignature(CalleeFTy);
- if (InvokeWrappers.find(Sig) != InvokeWrappers.end())
- return InvokeWrappers[Sig];
- // Put the pointer to the callee as first argument
- ArgTys.push_back(PointerType::getUnqual(CalleeFTy));
- // Add argument types
- ArgTys.append(CalleeFTy->param_begin(), CalleeFTy->param_end());
- FunctionType *FTy = FunctionType::get(CalleeFTy->getReturnType(), ArgTys,
- CalleeFTy->isVarArg());
- Function *F = getEmscriptenFunction(FTy, "__invoke_" + Sig, M);
- InvokeWrappers[Sig] = F;
- return F;
- }
- static bool canLongjmp(const Value *Callee) {
- if (auto *CalleeF = dyn_cast<Function>(Callee))
- if (CalleeF->isIntrinsic())
- return false;
- // Attempting to transform inline assembly will result in something like:
- // call void @__invoke_void(void ()* asm ...)
- // which is invalid because inline assembly blocks do not have addresses
- // and can't be passed by pointer. The result is a crash with illegal IR.
- if (isa<InlineAsm>(Callee))
- return false;
- StringRef CalleeName = Callee->getName();
- // TODO Include more functions or consider checking with mangled prefixes
- // The reason we include malloc/free here is to exclude the malloc/free
- // calls generated in setjmp prep / cleanup routines.
- if (CalleeName == "setjmp" || CalleeName == "malloc" || CalleeName == "free")
- return false;
- // There are functions in Emscripten's JS glue code or compiler-rt
- if (CalleeName == "__resumeException" || CalleeName == "llvm_eh_typeid_for" ||
- CalleeName == "saveSetjmp" || CalleeName == "testSetjmp" ||
- CalleeName == "getTempRet0" || CalleeName == "setTempRet0")
- return false;
- // __cxa_find_matching_catch_N functions cannot longjmp
- if (Callee->getName().startswith("__cxa_find_matching_catch_"))
- return false;
- // Exception-catching related functions
- //
- // We intentionally treat __cxa_end_catch longjmpable in Wasm SjLj even though
- // it surely cannot longjmp, in order to maintain the unwind relationship from
- // all existing catchpads (and calls within them) to catch.dispatch.longjmp.
- //
- // In Wasm EH + Wasm SjLj, we
- // 1. Make all catchswitch and cleanuppad that unwind to caller unwind to
- // catch.dispatch.longjmp instead
- // 2. Convert all longjmpable calls to invokes that unwind to
- // catch.dispatch.longjmp
- // But catchswitch BBs are removed in isel, so if an EH catchswitch (generated
- // from an exception)'s catchpad does not contain any calls that are converted
- // into invokes unwinding to catch.dispatch.longjmp, this unwind relationship
- // (EH catchswitch BB -> catch.dispatch.longjmp BB) is lost and
- // catch.dispatch.longjmp BB can be placed before the EH catchswitch BB in
- // CFGSort.
- // int ret = setjmp(buf);
- // try {
- // foo(); // longjmps
- // } catch (...) {
- // }
- // Then in this code, if 'foo' longjmps, it first unwinds to 'catch (...)'
- // catchswitch, and is not caught by that catchswitch because it is a longjmp,
- // then it should next unwind to catch.dispatch.longjmp BB. But if this 'catch
- // (...)' catchswitch -> catch.dispatch.longjmp unwind relationship is lost,
- // it will not unwind to catch.dispatch.longjmp, producing an incorrect
- // result.
- //
- // Every catchpad generated by Wasm C++ contains __cxa_end_catch, so we
- // intentionally treat it as longjmpable to work around this problem. This is
- // a hacky fix but an easy one.
- //
- // The comment block in findWasmUnwindDestinations() in
- // SelectionDAGBuilder.cpp is addressing a similar problem.
- if (CalleeName == "__cxa_end_catch")
- return WebAssembly::WasmEnableSjLj;
- if (CalleeName == "__cxa_begin_catch" ||
- CalleeName == "__cxa_allocate_exception" || CalleeName == "__cxa_throw" ||
- CalleeName == "__clang_call_terminate")
- return false;
- // std::terminate, which is generated when another exception occurs while
- // handling an exception, cannot longjmp.
- if (CalleeName == "_ZSt9terminatev")
- return false;
- // Otherwise we don't know
- return true;
- }
- static bool isEmAsmCall(const Value *Callee) {
- StringRef CalleeName = Callee->getName();
- // This is an exhaustive list from Emscripten's <emscripten/em_asm.h>.
- return CalleeName == "emscripten_asm_const_int" ||
- CalleeName == "emscripten_asm_const_double" ||
- CalleeName == "emscripten_asm_const_int_sync_on_main_thread" ||
- CalleeName == "emscripten_asm_const_double_sync_on_main_thread" ||
- CalleeName == "emscripten_asm_const_async_on_main_thread";
- }
- // Generate testSetjmp function call seqence with preamble and postamble.
- // The code this generates is equivalent to the following JavaScript code:
- // %__threwValue.val = __threwValue;
- // if (%__THREW__.val != 0 & %__threwValue.val != 0) {
- // %label = testSetjmp(mem[%__THREW__.val], setjmpTable, setjmpTableSize);
- // if (%label == 0)
- // emscripten_longjmp(%__THREW__.val, %__threwValue.val);
- // setTempRet0(%__threwValue.val);
- // } else {
- // %label = -1;
- // }
- // %longjmp_result = getTempRet0();
- //
- // As output parameters. returns %label, %longjmp_result, and the BB the last
- // instruction (%longjmp_result = ...) is in.
- void WebAssemblyLowerEmscriptenEHSjLj::wrapTestSetjmp(
- BasicBlock *BB, DebugLoc DL, Value *Threw, Value *SetjmpTable,
- Value *SetjmpTableSize, Value *&Label, Value *&LongjmpResult,
- BasicBlock *&CallEmLongjmpBB, PHINode *&CallEmLongjmpBBThrewPHI,
- PHINode *&CallEmLongjmpBBThrewValuePHI, BasicBlock *&EndBB) {
- Function *F = BB->getParent();
- Module *M = F->getParent();
- LLVMContext &C = M->getContext();
- IRBuilder<> IRB(C);
- IRB.SetCurrentDebugLocation(DL);
- // if (%__THREW__.val != 0 & %__threwValue.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 *ThrewCmp = IRB.CreateICmpNE(Threw, getAddrSizeInt(M, 0));
- Value *ThrewValue = IRB.CreateLoad(IRB.getInt32Ty(), ThrewValueGV,
- ThrewValueGV->getName() + ".val");
- Value *ThrewValueCmp = IRB.CreateICmpNE(ThrewValue, IRB.getInt32(0));
- Value *Cmp1 = IRB.CreateAnd(ThrewCmp, ThrewValueCmp, "cmp1");
- IRB.CreateCondBr(Cmp1, ThenBB1, ElseBB1);
- // Generate call.em.longjmp BB once and share it within the function
- if (!CallEmLongjmpBB) {
- // emscripten_longjmp(%__THREW__.val, %__threwValue.val);
- CallEmLongjmpBB = BasicBlock::Create(C, "call.em.longjmp", F);
- IRB.SetInsertPoint(CallEmLongjmpBB);
- CallEmLongjmpBBThrewPHI = IRB.CreatePHI(getAddrIntType(M), 4, "threw.phi");
- CallEmLongjmpBBThrewValuePHI =
- IRB.CreatePHI(IRB.getInt32Ty(), 4, "threwvalue.phi");
- CallEmLongjmpBBThrewPHI->addIncoming(Threw, ThenBB1);
- CallEmLongjmpBBThrewValuePHI->addIncoming(ThrewValue, ThenBB1);
- IRB.CreateCall(EmLongjmpF,
- {CallEmLongjmpBBThrewPHI, CallEmLongjmpBBThrewValuePHI});
- IRB.CreateUnreachable();
- } else {
- CallEmLongjmpBBThrewPHI->addIncoming(Threw, ThenBB1);
- CallEmLongjmpBBThrewValuePHI->addIncoming(ThrewValue, ThenBB1);
- }
- // %label = testSetjmp(mem[%__THREW__.val], setjmpTable, setjmpTableSize);
- // if (%label == 0)
- IRB.SetInsertPoint(ThenBB1);
- BasicBlock *EndBB2 = BasicBlock::Create(C, "if.end2", F);
- Value *ThrewPtr =
- IRB.CreateIntToPtr(Threw, getAddrPtrType(M), Threw->getName() + ".p");
- Value *LoadedThrew = IRB.CreateLoad(getAddrIntType(M), ThrewPtr,
- ThrewPtr->getName() + ".loaded");
- Value *ThenLabel = IRB.CreateCall(
- TestSetjmpF, {LoadedThrew, SetjmpTable, SetjmpTableSize}, "label");
- Value *Cmp2 = IRB.CreateICmpEQ(ThenLabel, IRB.getInt32(0));
- IRB.CreateCondBr(Cmp2, CallEmLongjmpBB, EndBB2);
- // setTempRet0(%__threwValue.val);
- IRB.SetInsertPoint(EndBB2);
- IRB.CreateCall(SetTempRet0F, ThrewValue);
- IRB.CreateBr(EndBB1);
- IRB.SetInsertPoint(ElseBB1);
- IRB.CreateBr(EndBB1);
- // longjmp_result = getTempRet0();
- 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.CreateCall(GetTempRet0F, std::nullopt, "longjmp_result");
- }
- void WebAssemblyLowerEmscriptenEHSjLj::rebuildSSA(Function &F) {
- DominatorTree &DT = getAnalysis<DominatorTreeWrapperPass>(F).getDomTree();
- DT.recalculate(F); // CFG has been changed
- SSAUpdaterBulk SSA;
- for (BasicBlock &BB : F) {
- for (Instruction &I : BB) {
- unsigned VarID = SSA.AddVariable(I.getName(), I.getType());
- // If a value is defined by an invoke instruction, it is only available in
- // its normal destination and not in its unwind destination.
- if (auto *II = dyn_cast<InvokeInst>(&I))
- SSA.AddAvailableValue(VarID, II->getNormalDest(), II);
- else
- SSA.AddAvailableValue(VarID, &BB, &I);
- for (auto &U : I.uses()) {
- auto *User = cast<Instruction>(U.getUser());
- if (auto *UserPN = dyn_cast<PHINode>(User))
- if (UserPN->getIncomingBlock(U) == &BB)
- continue;
- if (DT.dominates(&I, User))
- continue;
- SSA.AddUse(VarID, &U);
- }
- }
- }
- SSA.RewriteAllUses(&DT);
- }
- // Replace uses of longjmp with a new longjmp function in Emscripten library.
- // In Emscripten SjLj, the new function is
- // void emscripten_longjmp(uintptr_t, i32)
- // In Wasm SjLj, the new function is
- // void __wasm_longjmp(i8*, i32)
- // Because the original libc longjmp function takes (jmp_buf*, i32), we need a
- // ptrtoint/bitcast instruction here to make the type match. jmp_buf* will
- // eventually be lowered to i32/i64 in the wasm backend.
- void WebAssemblyLowerEmscriptenEHSjLj::replaceLongjmpWith(Function *LongjmpF,
- Function *NewF) {
- assert(NewF == EmLongjmpF || NewF == WasmLongjmpF);
- Module *M = LongjmpF->getParent();
- SmallVector<CallInst *, 8> ToErase;
- LLVMContext &C = LongjmpF->getParent()->getContext();
- IRBuilder<> IRB(C);
- // For calls to longjmp, replace it with emscripten_longjmp/__wasm_longjmp and
- // cast its first argument (jmp_buf*) appropriately
- for (User *U : LongjmpF->users()) {
- auto *CI = dyn_cast<CallInst>(U);
- if (CI && CI->getCalledFunction() == LongjmpF) {
- IRB.SetInsertPoint(CI);
- Value *Env = nullptr;
- if (NewF == EmLongjmpF)
- Env =
- IRB.CreatePtrToInt(CI->getArgOperand(0), getAddrIntType(M), "env");
- else // WasmLongjmpF
- Env =
- IRB.CreateBitCast(CI->getArgOperand(0), IRB.getInt8PtrTy(), "env");
- IRB.CreateCall(NewF, {Env, CI->getArgOperand(1)});
- ToErase.push_back(CI);
- }
- }
- for (auto *I : ToErase)
- I->eraseFromParent();
- // If we have any remaining uses of longjmp's function pointer, replace it
- // with (void(*)(jmp_buf*, int))emscripten_longjmp / __wasm_longjmp.
- if (!LongjmpF->uses().empty()) {
- Value *NewLongjmp =
- IRB.CreateBitCast(NewF, LongjmpF->getType(), "longjmp.cast");
- LongjmpF->replaceAllUsesWith(NewLongjmp);
- }
- }
- static bool containsLongjmpableCalls(const Function *F) {
- for (const auto &BB : *F)
- for (const auto &I : BB)
- if (const auto *CB = dyn_cast<CallBase>(&I))
- if (canLongjmp(CB->getCalledOperand()))
- return true;
- return false;
- }
- // When a function contains a setjmp call but not other calls that can longjmp,
- // we don't do setjmp transformation for that setjmp. But we need to convert the
- // setjmp calls into "i32 0" so they don't cause link time errors. setjmp always
- // returns 0 when called directly.
- static void nullifySetjmp(Function *F) {
- Module &M = *F->getParent();
- IRBuilder<> IRB(M.getContext());
- Function *SetjmpF = M.getFunction("setjmp");
- SmallVector<Instruction *, 1> ToErase;
- for (User *U : make_early_inc_range(SetjmpF->users())) {
- auto *CB = cast<CallBase>(U);
- BasicBlock *BB = CB->getParent();
- if (BB->getParent() != F) // in other function
- continue;
- CallInst *CI = nullptr;
- // setjmp cannot throw. So if it is an invoke, lower it to a call
- if (auto *II = dyn_cast<InvokeInst>(CB))
- CI = llvm::changeToCall(II);
- else
- CI = cast<CallInst>(CB);
- ToErase.push_back(CI);
- CI->replaceAllUsesWith(IRB.getInt32(0));
- }
- for (auto *I : ToErase)
- I->eraseFromParent();
- }
- bool WebAssemblyLowerEmscriptenEHSjLj::runOnModule(Module &M) {
- LLVM_DEBUG(dbgs() << "********** Lower Emscripten EH & SjLj **********\n");
- LLVMContext &C = M.getContext();
- IRBuilder<> IRB(C);
- Function *SetjmpF = M.getFunction("setjmp");
- Function *LongjmpF = M.getFunction("longjmp");
- // In some platforms _setjmp and _longjmp are used instead. Change these to
- // use setjmp/longjmp instead, because we later detect these functions by
- // their names.
- Function *SetjmpF2 = M.getFunction("_setjmp");
- Function *LongjmpF2 = M.getFunction("_longjmp");
- if (SetjmpF2) {
- if (SetjmpF) {
- if (SetjmpF->getFunctionType() != SetjmpF2->getFunctionType())
- report_fatal_error("setjmp and _setjmp have different function types");
- } else {
- SetjmpF = Function::Create(SetjmpF2->getFunctionType(),
- GlobalValue::ExternalLinkage, "setjmp", M);
- }
- SetjmpF2->replaceAllUsesWith(SetjmpF);
- }
- if (LongjmpF2) {
- if (LongjmpF) {
- if (LongjmpF->getFunctionType() != LongjmpF2->getFunctionType())
- report_fatal_error(
- "longjmp and _longjmp have different function types");
- } else {
- LongjmpF = Function::Create(LongjmpF2->getFunctionType(),
- GlobalValue::ExternalLinkage, "setjmp", M);
- }
- LongjmpF2->replaceAllUsesWith(LongjmpF);
- }
- auto *TPC = getAnalysisIfAvailable<TargetPassConfig>();
- assert(TPC && "Expected a TargetPassConfig");
- auto &TM = TPC->getTM<WebAssemblyTargetMachine>();
- // Declare (or get) global variables __THREW__, __threwValue, and
- // getTempRet0/setTempRet0 function which are used in common for both
- // exception handling and setjmp/longjmp handling
- ThrewGV = getGlobalVariable(M, getAddrIntType(&M), TM, "__THREW__");
- ThrewValueGV = getGlobalVariable(M, IRB.getInt32Ty(), TM, "__threwValue");
- GetTempRet0F = getEmscriptenFunction(
- FunctionType::get(IRB.getInt32Ty(), false), "getTempRet0", &M);
- SetTempRet0F = getEmscriptenFunction(
- FunctionType::get(IRB.getVoidTy(), IRB.getInt32Ty(), false),
- "setTempRet0", &M);
- GetTempRet0F->setDoesNotThrow();
- SetTempRet0F->setDoesNotThrow();
- bool Changed = false;
- // Function registration for exception handling
- if (EnableEmEH) {
- // Register __resumeException function
- FunctionType *ResumeFTy =
- FunctionType::get(IRB.getVoidTy(), IRB.getInt8PtrTy(), false);
- ResumeF = getEmscriptenFunction(ResumeFTy, "__resumeException", &M);
- ResumeF->addFnAttr(Attribute::NoReturn);
- // Register llvm_eh_typeid_for function
- FunctionType *EHTypeIDTy =
- FunctionType::get(IRB.getInt32Ty(), IRB.getInt8PtrTy(), false);
- EHTypeIDF = getEmscriptenFunction(EHTypeIDTy, "llvm_eh_typeid_for", &M);
- }
- // Functions that contains calls to setjmp but don't have other longjmpable
- // calls within them.
- SmallPtrSet<Function *, 4> SetjmpUsersToNullify;
- if ((EnableEmSjLj || EnableWasmSjLj) && SetjmpF) {
- // Precompute setjmp users
- for (User *U : SetjmpF->users()) {
- if (auto *CB = dyn_cast<CallBase>(U)) {
- auto *UserF = CB->getFunction();
- // If a function that calls setjmp does not contain any other calls that
- // can longjmp, we don't need to do any transformation on that function,
- // so can ignore it
- if (containsLongjmpableCalls(UserF))
- SetjmpUsers.insert(UserF);
- else
- SetjmpUsersToNullify.insert(UserF);
- } else {
- std::string S;
- raw_string_ostream SS(S);
- SS << *U;
- report_fatal_error(Twine("Indirect use of setjmp is not supported: ") +
- SS.str());
- }
- }
- }
- bool SetjmpUsed = SetjmpF && !SetjmpUsers.empty();
- bool LongjmpUsed = LongjmpF && !LongjmpF->use_empty();
- DoSjLj = (EnableEmSjLj | EnableWasmSjLj) && (SetjmpUsed || LongjmpUsed);
- // Function registration and data pre-gathering for setjmp/longjmp handling
- if (DoSjLj) {
- assert(EnableEmSjLj || EnableWasmSjLj);
- if (EnableEmSjLj) {
- // Register emscripten_longjmp function
- FunctionType *FTy = FunctionType::get(
- IRB.getVoidTy(), {getAddrIntType(&M), IRB.getInt32Ty()}, false);
- EmLongjmpF = getEmscriptenFunction(FTy, "emscripten_longjmp", &M);
- EmLongjmpF->addFnAttr(Attribute::NoReturn);
- } else { // EnableWasmSjLj
- // Register __wasm_longjmp function, which calls __builtin_wasm_longjmp.
- FunctionType *FTy = FunctionType::get(
- IRB.getVoidTy(), {IRB.getInt8PtrTy(), IRB.getInt32Ty()}, false);
- WasmLongjmpF = getEmscriptenFunction(FTy, "__wasm_longjmp", &M);
- WasmLongjmpF->addFnAttr(Attribute::NoReturn);
- }
- if (SetjmpF) {
- // Register saveSetjmp function
- FunctionType *SetjmpFTy = SetjmpF->getFunctionType();
- FunctionType *FTy =
- FunctionType::get(Type::getInt32PtrTy(C),
- {SetjmpFTy->getParamType(0), IRB.getInt32Ty(),
- Type::getInt32PtrTy(C), IRB.getInt32Ty()},
- false);
- SaveSetjmpF = getEmscriptenFunction(FTy, "saveSetjmp", &M);
- // Register testSetjmp function
- FTy = FunctionType::get(
- IRB.getInt32Ty(),
- {getAddrIntType(&M), Type::getInt32PtrTy(C), IRB.getInt32Ty()},
- false);
- TestSetjmpF = getEmscriptenFunction(FTy, "testSetjmp", &M);
- // wasm.catch() will be lowered down to wasm 'catch' instruction in
- // instruction selection.
- CatchF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_catch);
- // Type for struct __WasmLongjmpArgs
- LongjmpArgsTy = StructType::get(IRB.getInt8PtrTy(), // env
- IRB.getInt32Ty() // val
- );
- }
- }
- // Exception handling transformation
- if (EnableEmEH) {
- for (Function &F : M) {
- if (F.isDeclaration())
- continue;
- Changed |= runEHOnFunction(F);
- }
- }
- // Setjmp/longjmp handling transformation
- if (DoSjLj) {
- Changed = true; // We have setjmp or longjmp somewhere
- if (LongjmpF)
- replaceLongjmpWith(LongjmpF, EnableEmSjLj ? EmLongjmpF : WasmLongjmpF);
- // Only traverse functions that uses setjmp in order not to insert
- // unnecessary prep / cleanup code in every function
- if (SetjmpF)
- for (Function *F : SetjmpUsers)
- runSjLjOnFunction(*F);
- }
- // Replace unnecessary setjmp calls with 0
- if ((EnableEmSjLj || EnableWasmSjLj) && !SetjmpUsersToNullify.empty()) {
- Changed = true;
- assert(SetjmpF);
- for (Function *F : SetjmpUsersToNullify)
- nullifySetjmp(F);
- }
- // Delete unused global variables and functions
- for (auto *V : {ThrewGV, ThrewValueGV})
- if (V && V->use_empty())
- V->eraseFromParent();
- for (auto *V : {GetTempRet0F, SetTempRet0F, ResumeF, EHTypeIDF, EmLongjmpF,
- SaveSetjmpF, TestSetjmpF, WasmLongjmpF, CatchF})
- if (V && V->use_empty())
- V->eraseFromParent();
- return Changed;
- }
- bool WebAssemblyLowerEmscriptenEHSjLj::runEHOnFunction(Function &F) {
- Module &M = *F.getParent();
- LLVMContext &C = F.getContext();
- IRBuilder<> IRB(C);
- bool Changed = false;
- SmallVector<Instruction *, 64> ToErase;
- SmallPtrSet<LandingPadInst *, 32> LandingPads;
- // rethrow.longjmp BB that will be shared within the function.
- BasicBlock *RethrowLongjmpBB = nullptr;
- // PHI node for the loaded value of __THREW__ global variable in
- // rethrow.longjmp BB
- PHINode *RethrowLongjmpBBThrewPHI = nullptr;
- for (BasicBlock &BB : F) {
- auto *II = dyn_cast<InvokeInst>(BB.getTerminator());
- if (!II)
- continue;
- Changed = true;
- LandingPads.insert(II->getLandingPadInst());
- IRB.SetInsertPoint(II);
- const Value *Callee = II->getCalledOperand();
- bool NeedInvoke = supportsException(&F) && canThrow(Callee);
- if (NeedInvoke) {
- // Wrap invoke with invoke wrapper and generate preamble/postamble
- Value *Threw = wrapInvoke(II);
- ToErase.push_back(II);
- // If setjmp/longjmp handling is enabled, the thrown value can be not an
- // exception but a longjmp. If the current function contains calls to
- // setjmp, it will be appropriately handled in runSjLjOnFunction. But even
- // if the function does not contain setjmp calls, we shouldn't silently
- // ignore longjmps; we should rethrow them so they can be correctly
- // handled in somewhere up the call chain where setjmp is. __THREW__'s
- // value is 0 when nothing happened, 1 when an exception is thrown, and
- // other values when longjmp is thrown.
- //
- // if (%__THREW__.val == 0 || %__THREW__.val == 1)
- // goto %tail
- // else
- // goto %longjmp.rethrow
- //
- // rethrow.longjmp: ;; This is longjmp. Rethrow it
- // %__threwValue.val = __threwValue
- // emscripten_longjmp(%__THREW__.val, %__threwValue.val);
- //
- // tail: ;; Nothing happened or an exception is thrown
- // ... Continue exception handling ...
- if (DoSjLj && EnableEmSjLj && !SetjmpUsers.count(&F) &&
- canLongjmp(Callee)) {
- // Create longjmp.rethrow BB once and share it within the function
- if (!RethrowLongjmpBB) {
- RethrowLongjmpBB = BasicBlock::Create(C, "rethrow.longjmp", &F);
- IRB.SetInsertPoint(RethrowLongjmpBB);
- RethrowLongjmpBBThrewPHI =
- IRB.CreatePHI(getAddrIntType(&M), 4, "threw.phi");
- RethrowLongjmpBBThrewPHI->addIncoming(Threw, &BB);
- Value *ThrewValue = IRB.CreateLoad(IRB.getInt32Ty(), ThrewValueGV,
- ThrewValueGV->getName() + ".val");
- IRB.CreateCall(EmLongjmpF, {RethrowLongjmpBBThrewPHI, ThrewValue});
- IRB.CreateUnreachable();
- } else {
- RethrowLongjmpBBThrewPHI->addIncoming(Threw, &BB);
- }
- IRB.SetInsertPoint(II); // Restore the insert point back
- BasicBlock *Tail = BasicBlock::Create(C, "tail", &F);
- Value *CmpEqOne =
- IRB.CreateICmpEQ(Threw, getAddrSizeInt(&M, 1), "cmp.eq.one");
- Value *CmpEqZero =
- IRB.CreateICmpEQ(Threw, getAddrSizeInt(&M, 0), "cmp.eq.zero");
- Value *Or = IRB.CreateOr(CmpEqZero, CmpEqOne, "or");
- IRB.CreateCondBr(Or, Tail, RethrowLongjmpBB);
- IRB.SetInsertPoint(Tail);
- BB.replaceSuccessorsPhiUsesWith(&BB, Tail);
- }
- // Insert a branch based on __THREW__ variable
- Value *Cmp = IRB.CreateICmpEQ(Threw, getAddrSizeInt(&M, 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
- changeToCall(II);
- }
- }
- // Process resume instructions
- for (BasicBlock &BB : F) {
- // Scan the body of the basic block for resumes
- for (Instruction &I : BB) {
- auto *RI = dyn_cast<ResumeInst>(&I);
- if (!RI)
- continue;
- Changed = true;
- // Split the input into legal values
- Value *Input = RI->getValue();
- IRB.SetInsertPoint(RI);
- Value *Low = IRB.CreateExtractValue(Input, 0, "low");
- // Create a call to __resumeException function
- IRB.CreateCall(ResumeF, {Low});
- // Add a terminator to the block
- IRB.CreateUnreachable();
- ToErase.push_back(RI);
- }
- }
- // Process llvm.eh.typeid.for intrinsics
- for (BasicBlock &BB : F) {
- for (Instruction &I : BB) {
- auto *CI = dyn_cast<CallInst>(&I);
- if (!CI)
- continue;
- const Function *Callee = CI->getCalledFunction();
- if (!Callee)
- continue;
- if (Callee->getIntrinsicID() != Intrinsic::eh_typeid_for)
- continue;
- Changed = true;
- IRB.SetInsertPoint(CI);
- CallInst *NewCI =
- IRB.CreateCall(EHTypeIDF, CI->getArgOperand(0), "typeid");
- CI->replaceAllUsesWith(NewCI);
- ToErase.push_back(CI);
- }
- }
- // Look for orphan landingpads, can occur in blocks with no predecessors
- for (BasicBlock &BB : F) {
- Instruction *I = BB.getFirstNonPHI();
- if (auto *LPI = dyn_cast<LandingPadInst>(I))
- LandingPads.insert(LPI);
- }
- Changed |= !LandingPads.empty();
- // Handle all the landingpad for this function together, as multiple invokes
- // may share a single lp
- for (LandingPadInst *LPI : LandingPads) {
- IRB.SetInsertPoint(LPI);
- SmallVector<Value *, 16> FMCArgs;
- for (unsigned I = 0, E = LPI->getNumClauses(); I < E; ++I) {
- Constant *Clause = LPI->getClause(I);
- // TODO Handle filters (= exception specifications).
- // https://bugs.llvm.org/show_bug.cgi?id=50396
- if (LPI->isCatch(I))
- FMCArgs.push_back(Clause);
- }
- // Create a call to __cxa_find_matching_catch_N function
- Function *FMCF = getFindMatchingCatch(M, FMCArgs.size());
- CallInst *FMCI = IRB.CreateCall(FMCF, FMCArgs, "fmc");
- Value *Poison = PoisonValue::get(LPI->getType());
- Value *Pair0 = IRB.CreateInsertValue(Poison, FMCI, 0, "pair0");
- Value *TempRet0 = IRB.CreateCall(GetTempRet0F, std::nullopt, "tempret0");
- Value *Pair1 = IRB.CreateInsertValue(Pair0, TempRet0, 1, "pair1");
- LPI->replaceAllUsesWith(Pair1);
- ToErase.push_back(LPI);
- }
- // Erase everything we no longer need in this function
- for (Instruction *I : ToErase)
- I->eraseFromParent();
- return Changed;
- }
- // This tries to get debug info from the instruction before which a new
- // instruction will be inserted, and if there's no debug info in that
- // instruction, tries to get the info instead from the previous instruction (if
- // any). If none of these has debug info and a DISubprogram is provided, it
- // creates a dummy debug info with the first line of the function, because IR
- // verifier requires all inlinable callsites should have debug info when both a
- // caller and callee have DISubprogram. If none of these conditions are met,
- // returns empty info.
- static DebugLoc getOrCreateDebugLoc(const Instruction *InsertBefore,
- DISubprogram *SP) {
- assert(InsertBefore);
- if (InsertBefore->getDebugLoc())
- return InsertBefore->getDebugLoc();
- const Instruction *Prev = InsertBefore->getPrevNode();
- if (Prev && Prev->getDebugLoc())
- return Prev->getDebugLoc();
- if (SP)
- return DILocation::get(SP->getContext(), SP->getLine(), 1, SP);
- return DebugLoc();
- }
- bool WebAssemblyLowerEmscriptenEHSjLj::runSjLjOnFunction(Function &F) {
- assert(EnableEmSjLj || EnableWasmSjLj);
- Module &M = *F.getParent();
- LLVMContext &C = F.getContext();
- IRBuilder<> IRB(C);
- SmallVector<Instruction *, 64> ToErase;
- // Vector of %setjmpTable values
- SmallVector<Instruction *, 4> SetjmpTableInsts;
- // Vector of %setjmpTableSize values
- SmallVector<Instruction *, 4> SetjmpTableSizeInsts;
- // Setjmp preparation
- // This instruction effectively means %setjmpTableSize = 4.
- // We create this as an instruction intentionally, and we don't want to fold
- // this instruction to a constant 4, because this value will be used in
- // SSAUpdater.AddAvailableValue(...) later.
- BasicBlock *Entry = &F.getEntryBlock();
- DebugLoc FirstDL = getOrCreateDebugLoc(&*Entry->begin(), F.getSubprogram());
- SplitBlock(Entry, &*Entry->getFirstInsertionPt());
- BinaryOperator *SetjmpTableSize =
- BinaryOperator::Create(Instruction::Add, IRB.getInt32(4), IRB.getInt32(0),
- "setjmpTableSize", Entry->getTerminator());
- SetjmpTableSize->setDebugLoc(FirstDL);
- // setjmpTable = (int *) malloc(40);
- Type *IntPtrTy = getAddrIntType(&M);
- Constant *size = ConstantInt::get(IntPtrTy, 40);
- Instruction *SetjmpTable =
- CallInst::CreateMalloc(SetjmpTableSize, IntPtrTy, IRB.getInt32Ty(), size,
- nullptr, nullptr, "setjmpTable");
- SetjmpTable->setDebugLoc(FirstDL);
- // CallInst::CreateMalloc may return a bitcast instruction if the result types
- // mismatch. We need to set the debug loc for the original call too.
- auto *MallocCall = SetjmpTable->stripPointerCasts();
- if (auto *MallocCallI = dyn_cast<Instruction>(MallocCall)) {
- MallocCallI->setDebugLoc(FirstDL);
- }
- // setjmpTable[0] = 0;
- IRB.SetInsertPoint(SetjmpTableSize);
- IRB.CreateStore(IRB.getInt32(0), SetjmpTable);
- SetjmpTableInsts.push_back(SetjmpTable);
- SetjmpTableSizeInsts.push_back(SetjmpTableSize);
- // Setjmp transformation
- SmallVector<PHINode *, 4> SetjmpRetPHIs;
- Function *SetjmpF = M.getFunction("setjmp");
- for (auto *U : make_early_inc_range(SetjmpF->users())) {
- auto *CB = cast<CallBase>(U);
- BasicBlock *BB = CB->getParent();
- if (BB->getParent() != &F) // in other function
- continue;
- if (CB->getOperandBundle(LLVMContext::OB_funclet)) {
- std::string S;
- raw_string_ostream SS(S);
- SS << "In function " + F.getName() +
- ": setjmp within a catch clause is not supported in Wasm EH:\n";
- SS << *CB;
- report_fatal_error(StringRef(SS.str()));
- }
- CallInst *CI = nullptr;
- // setjmp cannot throw. So if it is an invoke, lower it to a call
- if (auto *II = dyn_cast<InvokeInst>(CB))
- CI = llvm::changeToCall(II);
- else
- CI = cast<CallInst>(CB);
- // 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.CreateCall(GetTempRet0F, std::nullopt, "setjmpTableSize");
- SetjmpTableInsts.push_back(NewSetjmpTable);
- SetjmpTableSizeInsts.push_back(NewSetjmpTableSize);
- ToErase.push_back(CI);
- }
- // Handle longjmpable calls.
- if (EnableEmSjLj)
- handleLongjmpableCallsForEmscriptenSjLj(
- F, SetjmpTableInsts, SetjmpTableSizeInsts, SetjmpRetPHIs);
- else // EnableWasmSjLj
- handleLongjmpableCallsForWasmSjLj(F, SetjmpTableInsts, SetjmpTableSizeInsts,
- SetjmpRetPHIs);
- // Erase everything we no longer need in this function
- for (Instruction *I : ToErase)
- I->eraseFromParent();
- // Free setjmpTable buffer before each return instruction + function-exiting
- // call
- SmallVector<Instruction *, 16> ExitingInsts;
- for (BasicBlock &BB : F) {
- Instruction *TI = BB.getTerminator();
- if (isa<ReturnInst>(TI))
- ExitingInsts.push_back(TI);
- // Any 'call' instruction with 'noreturn' attribute exits the function at
- // this point. If this throws but unwinds to another EH pad within this
- // function instead of exiting, this would have been an 'invoke', which
- // happens if we use Wasm EH or Wasm SjLJ.
- for (auto &I : BB) {
- if (auto *CI = dyn_cast<CallInst>(&I)) {
- bool IsNoReturn = CI->hasFnAttr(Attribute::NoReturn);
- if (Function *CalleeF = CI->getCalledFunction())
- IsNoReturn |= CalleeF->hasFnAttribute(Attribute::NoReturn);
- if (IsNoReturn)
- ExitingInsts.push_back(&I);
- }
- }
- }
- for (auto *I : ExitingInsts) {
- DebugLoc DL = getOrCreateDebugLoc(I, F.getSubprogram());
- // If this existing instruction is a call within a catchpad, we should add
- // it as "funclet" to the operand bundle of 'free' call
- SmallVector<OperandBundleDef, 1> Bundles;
- if (auto *CB = dyn_cast<CallBase>(I))
- if (auto Bundle = CB->getOperandBundle(LLVMContext::OB_funclet))
- Bundles.push_back(OperandBundleDef(*Bundle));
- auto *Free = CallInst::CreateFree(SetjmpTable, Bundles, I);
- Free->setDebugLoc(DL);
- // CallInst::CreateFree may create a bitcast instruction if its argument
- // types mismatch. We need to set the debug loc for the bitcast too.
- if (auto *FreeCallI = dyn_cast<CallInst>(Free)) {
- if (auto *BitCastI = dyn_cast<BitCastInst>(FreeCallI->getArgOperand(0)))
- BitCastI->setDebugLoc(DL);
- }
- }
- // 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(env, label, setjmpTable, setjmpTableSize);
- // setjmpTableSize = getTempRet0();
- // 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 &U : make_early_inc_range(SetjmpTable->uses()))
- if (auto *I = dyn_cast<Instruction>(U.getUser()))
- if (I->getParent() != Entry)
- SetjmpTableSSA.RewriteUse(U);
- for (auto &U : make_early_inc_range(SetjmpTableSize->uses()))
- if (auto *I = dyn_cast<Instruction>(U.getUser()))
- if (I->getParent() != Entry)
- 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;
- }
- // Update each call that can longjmp so it can return to the corresponding
- // setjmp. Refer to 4) of "Emscripten setjmp/longjmp handling" section in the
- // comments at top of the file for details.
- void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForEmscriptenSjLj(
- Function &F, InstVector &SetjmpTableInsts, InstVector &SetjmpTableSizeInsts,
- SmallVectorImpl<PHINode *> &SetjmpRetPHIs) {
- Module &M = *F.getParent();
- LLVMContext &C = F.getContext();
- IRBuilder<> IRB(C);
- SmallVector<Instruction *, 64> ToErase;
- // We need to pass setjmpTable and setjmpTableSize to testSetjmp function.
- // These values are defined in the beginning of the function and also in each
- // setjmp callsite, but we don't know which values we should use at this
- // point. So here we arbitraily use the ones defined in the beginning of the
- // function, and SSAUpdater will later update them to the correct values.
- Instruction *SetjmpTable = *SetjmpTableInsts.begin();
- Instruction *SetjmpTableSize = *SetjmpTableSizeInsts.begin();
- // call.em.longjmp BB that will be shared within the function.
- BasicBlock *CallEmLongjmpBB = nullptr;
- // PHI node for the loaded value of __THREW__ global variable in
- // call.em.longjmp BB
- PHINode *CallEmLongjmpBBThrewPHI = nullptr;
- // PHI node for the loaded value of __threwValue global variable in
- // call.em.longjmp BB
- PHINode *CallEmLongjmpBBThrewValuePHI = nullptr;
- // rethrow.exn BB that will be shared within the function.
- BasicBlock *RethrowExnBB = nullptr;
- // Because we are creating new BBs while processing and don't want to make
- // all these newly created BBs candidates again for longjmp processing, we
- // first make the vector of candidate BBs.
- std::vector<BasicBlock *> BBs;
- for (BasicBlock &BB : F)
- BBs.push_back(&BB);
- // BBs.size() will change within the loop, so we query it every time
- for (unsigned I = 0; I < BBs.size(); I++) {
- BasicBlock *BB = BBs[I];
- for (Instruction &I : *BB) {
- if (isa<InvokeInst>(&I)) {
- std::string S;
- raw_string_ostream SS(S);
- SS << "In function " << F.getName()
- << ": When using Wasm EH with Emscripten SjLj, there is a "
- "restriction that `setjmp` function call and exception cannot be "
- "used within the same function:\n";
- SS << I;
- report_fatal_error(StringRef(SS.str()));
- }
- auto *CI = dyn_cast<CallInst>(&I);
- if (!CI)
- continue;
- const Value *Callee = CI->getCalledOperand();
- if (!canLongjmp(Callee))
- continue;
- if (isEmAsmCall(Callee))
- report_fatal_error("Cannot use EM_ASM* alongside setjmp/longjmp in " +
- F.getName() +
- ". Please consider using EM_JS, or move the "
- "EM_ASM into another function.",
- false);
- Value *Threw = nullptr;
- BasicBlock *Tail;
- if (Callee->getName().startswith("__invoke_")) {
- // If invoke wrapper has already been generated for this call in
- // previous EH phase, search for the load instruction
- // %__THREW__.val = __THREW__;
- // in postamble after the invoke wrapper call
- LoadInst *ThrewLI = nullptr;
- StoreInst *ThrewResetSI = nullptr;
- for (auto I = std::next(BasicBlock::iterator(CI)), IE = BB->end();
- I != IE; ++I) {
- if (auto *LI = dyn_cast<LoadInst>(I))
- if (auto *GV = dyn_cast<GlobalVariable>(LI->getPointerOperand()))
- if (GV == ThrewGV) {
- Threw = ThrewLI = LI;
- break;
- }
- }
- // Search for the store instruction after the load above
- // __THREW__ = 0;
- for (auto I = std::next(BasicBlock::iterator(ThrewLI)), IE = BB->end();
- I != IE; ++I) {
- if (auto *SI = dyn_cast<StoreInst>(I)) {
- if (auto *GV = dyn_cast<GlobalVariable>(SI->getPointerOperand())) {
- if (GV == ThrewGV &&
- SI->getValueOperand() == getAddrSizeInt(&M, 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());
- // If exception handling is enabled, the thrown value can be not a
- // longjmp but an exception, in which case we shouldn't silently ignore
- // exceptions; we should rethrow them.
- // __THREW__'s value is 0 when nothing happened, 1 when an exception is
- // thrown, other values when longjmp is thrown.
- //
- // if (%__THREW__.val == 1)
- // goto %eh.rethrow
- // else
- // goto %normal
- //
- // eh.rethrow: ;; Rethrow exception
- // %exn = call @__cxa_find_matching_catch_2() ;; Retrieve thrown ptr
- // __resumeException(%exn)
- //
- // normal:
- // <-- Insertion point. Will insert sjlj handling code from here
- // goto %tail
- //
- // tail:
- // ...
- if (supportsException(&F) && canThrow(Callee)) {
- // We will add a new conditional branch. So remove the branch created
- // when we split the BB
- ToErase.push_back(BB->getTerminator());
- // Generate rethrow.exn BB once and share it within the function
- if (!RethrowExnBB) {
- RethrowExnBB = BasicBlock::Create(C, "rethrow.exn", &F);
- IRB.SetInsertPoint(RethrowExnBB);
- CallInst *Exn =
- IRB.CreateCall(getFindMatchingCatch(M, 0), {}, "exn");
- IRB.CreateCall(ResumeF, {Exn});
- IRB.CreateUnreachable();
- }
- IRB.SetInsertPoint(CI);
- BasicBlock *NormalBB = BasicBlock::Create(C, "normal", &F);
- Value *CmpEqOne =
- IRB.CreateICmpEQ(Threw, getAddrSizeInt(&M, 1), "cmp.eq.one");
- IRB.CreateCondBr(CmpEqOne, RethrowExnBB, NormalBB);
- IRB.SetInsertPoint(NormalBB);
- IRB.CreateBr(Tail);
- BB = NormalBB; // New insertion point to insert testSetjmp()
- }
- }
- // We need to replace the terminator in Tail - SplitBlock makes BB go
- // straight to Tail, we need to check if a longjmp occurred, and go to the
- // right setjmp-tail if so
- ToErase.push_back(BB->getTerminator());
- // Generate a function call to testSetjmp function and preamble/postamble
- // code to figure out (1) whether longjmp occurred (2) if longjmp
- // occurred, which setjmp it corresponds to
- Value *Label = nullptr;
- Value *LongjmpResult = nullptr;
- BasicBlock *EndBB = nullptr;
- wrapTestSetjmp(BB, CI->getDebugLoc(), Threw, SetjmpTable, SetjmpTableSize,
- Label, LongjmpResult, CallEmLongjmpBB,
- CallEmLongjmpBBThrewPHI, CallEmLongjmpBBThrewValuePHI,
- EndBB);
- assert(Label && LongjmpResult && EndBB);
- // Create switch instruction
- IRB.SetInsertPoint(EndBB);
- IRB.SetCurrentDebugLocation(EndBB->back().getDebugLoc());
- 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);
- }
- }
- for (Instruction *I : ToErase)
- I->eraseFromParent();
- }
- static BasicBlock *getCleanupRetUnwindDest(const CleanupPadInst *CPI) {
- for (const User *U : CPI->users())
- if (const auto *CRI = dyn_cast<CleanupReturnInst>(U))
- return CRI->getUnwindDest();
- return nullptr;
- }
- // Create a catchpad in which we catch a longjmp's env and val arguments, test
- // if the longjmp corresponds to one of setjmps in the current function, and if
- // so, jump to the setjmp dispatch BB from which we go to one of post-setjmp
- // BBs. Refer to 4) of "Wasm setjmp/longjmp handling" section in the comments at
- // top of the file for details.
- void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(
- Function &F, InstVector &SetjmpTableInsts, InstVector &SetjmpTableSizeInsts,
- SmallVectorImpl<PHINode *> &SetjmpRetPHIs) {
- Module &M = *F.getParent();
- LLVMContext &C = F.getContext();
- IRBuilder<> IRB(C);
- // A function with catchswitch/catchpad instruction should have a personality
- // function attached to it. Search for the wasm personality function, and if
- // it exists, use it, and if it doesn't, create a dummy personality function.
- // (SjLj is not going to call it anyway.)
- if (!F.hasPersonalityFn()) {
- StringRef PersName = getEHPersonalityName(EHPersonality::Wasm_CXX);
- FunctionType *PersType =
- FunctionType::get(IRB.getInt32Ty(), /* isVarArg */ true);
- Value *PersF = M.getOrInsertFunction(PersName, PersType).getCallee();
- F.setPersonalityFn(
- cast<Constant>(IRB.CreateBitCast(PersF, IRB.getInt8PtrTy())));
- }
- // Use the entry BB's debugloc as a fallback
- BasicBlock *Entry = &F.getEntryBlock();
- DebugLoc FirstDL = getOrCreateDebugLoc(&*Entry->begin(), F.getSubprogram());
- IRB.SetCurrentDebugLocation(FirstDL);
- // Arbitrarily use the ones defined in the beginning of the function.
- // SSAUpdater will later update them to the correct values.
- Instruction *SetjmpTable = *SetjmpTableInsts.begin();
- Instruction *SetjmpTableSize = *SetjmpTableSizeInsts.begin();
- // Add setjmp.dispatch BB right after the entry block. Because we have
- // initialized setjmpTable/setjmpTableSize in the entry block and split the
- // rest into another BB, here 'OrigEntry' is the function's original entry
- // block before the transformation.
- //
- // entry:
- // setjmpTable / setjmpTableSize initialization
- // setjmp.dispatch:
- // switch will be inserted here later
- // entry.split: (OrigEntry)
- // the original function starts here
- BasicBlock *OrigEntry = Entry->getNextNode();
- BasicBlock *SetjmpDispatchBB =
- BasicBlock::Create(C, "setjmp.dispatch", &F, OrigEntry);
- cast<BranchInst>(Entry->getTerminator())->setSuccessor(0, SetjmpDispatchBB);
- // Create catch.dispatch.longjmp BB and a catchswitch instruction
- BasicBlock *CatchDispatchLongjmpBB =
- BasicBlock::Create(C, "catch.dispatch.longjmp", &F);
- IRB.SetInsertPoint(CatchDispatchLongjmpBB);
- CatchSwitchInst *CatchSwitchLongjmp =
- IRB.CreateCatchSwitch(ConstantTokenNone::get(C), nullptr, 1);
- // Create catch.longjmp BB and a catchpad instruction
- BasicBlock *CatchLongjmpBB = BasicBlock::Create(C, "catch.longjmp", &F);
- CatchSwitchLongjmp->addHandler(CatchLongjmpBB);
- IRB.SetInsertPoint(CatchLongjmpBB);
- CatchPadInst *CatchPad = IRB.CreateCatchPad(CatchSwitchLongjmp, {});
- // Wasm throw and catch instructions can throw and catch multiple values, but
- // that requires multivalue support in the toolchain, which is currently not
- // very reliable. We instead throw and catch a pointer to a struct value of
- // type 'struct __WasmLongjmpArgs', which is defined in Emscripten.
- Instruction *CatchCI =
- IRB.CreateCall(CatchF, {IRB.getInt32(WebAssembly::C_LONGJMP)}, "thrown");
- Value *LongjmpArgs =
- IRB.CreateBitCast(CatchCI, LongjmpArgsTy->getPointerTo(), "longjmp.args");
- Value *EnvField =
- IRB.CreateConstGEP2_32(LongjmpArgsTy, LongjmpArgs, 0, 0, "env_gep");
- Value *ValField =
- IRB.CreateConstGEP2_32(LongjmpArgsTy, LongjmpArgs, 0, 1, "val_gep");
- // void *env = __wasm_longjmp_args.env;
- Instruction *Env = IRB.CreateLoad(IRB.getInt8PtrTy(), EnvField, "env");
- // int val = __wasm_longjmp_args.val;
- Instruction *Val = IRB.CreateLoad(IRB.getInt32Ty(), ValField, "val");
- // %label = testSetjmp(mem[%env], setjmpTable, setjmpTableSize);
- // if (%label == 0)
- // __wasm_longjmp(%env, %val)
- // catchret to %setjmp.dispatch
- BasicBlock *ThenBB = BasicBlock::Create(C, "if.then", &F);
- BasicBlock *EndBB = BasicBlock::Create(C, "if.end", &F);
- Value *EnvP = IRB.CreateBitCast(Env, getAddrPtrType(&M), "env.p");
- Value *SetjmpID = IRB.CreateLoad(getAddrIntType(&M), EnvP, "setjmp.id");
- Value *Label =
- IRB.CreateCall(TestSetjmpF, {SetjmpID, SetjmpTable, SetjmpTableSize},
- OperandBundleDef("funclet", CatchPad), "label");
- Value *Cmp = IRB.CreateICmpEQ(Label, IRB.getInt32(0));
- IRB.CreateCondBr(Cmp, ThenBB, EndBB);
- IRB.SetInsertPoint(ThenBB);
- CallInst *WasmLongjmpCI = IRB.CreateCall(
- WasmLongjmpF, {Env, Val}, OperandBundleDef("funclet", CatchPad));
- IRB.CreateUnreachable();
- IRB.SetInsertPoint(EndBB);
- // Jump to setjmp.dispatch block
- IRB.CreateCatchRet(CatchPad, SetjmpDispatchBB);
- // Go back to setjmp.dispatch BB
- // setjmp.dispatch:
- // switch %label {
- // label 1: goto post-setjmp BB 1
- // label 2: goto post-setjmp BB 2
- // ...
- // default: goto splitted next BB
- // }
- IRB.SetInsertPoint(SetjmpDispatchBB);
- PHINode *LabelPHI = IRB.CreatePHI(IRB.getInt32Ty(), 2, "label.phi");
- LabelPHI->addIncoming(Label, EndBB);
- LabelPHI->addIncoming(IRB.getInt32(-1), Entry);
- SwitchInst *SI = IRB.CreateSwitch(LabelPHI, OrigEntry, SetjmpRetPHIs.size());
- // -1 means no longjmp happened, continue normally (will hit the default
- // switch case). 0 means a longjmp that is not ours to handle, needs a
- // rethrow. Otherwise the index is the same as the index in P+1 (to avoid
- // 0).
- for (unsigned I = 0; I < SetjmpRetPHIs.size(); I++) {
- SI->addCase(IRB.getInt32(I + 1), SetjmpRetPHIs[I]->getParent());
- SetjmpRetPHIs[I]->addIncoming(Val, SetjmpDispatchBB);
- }
- // Convert all longjmpable call instructions to invokes that unwind to the
- // newly created catch.dispatch.longjmp BB.
- SmallVector<CallInst *, 64> LongjmpableCalls;
- for (auto *BB = &*F.begin(); BB; BB = BB->getNextNode()) {
- for (auto &I : *BB) {
- auto *CI = dyn_cast<CallInst>(&I);
- if (!CI)
- continue;
- const Value *Callee = CI->getCalledOperand();
- if (!canLongjmp(Callee))
- continue;
- if (isEmAsmCall(Callee))
- report_fatal_error("Cannot use EM_ASM* alongside setjmp/longjmp in " +
- F.getName() +
- ". Please consider using EM_JS, or move the "
- "EM_ASM into another function.",
- false);
- // This is __wasm_longjmp() call we inserted in this function, which
- // rethrows the longjmp when the longjmp does not correspond to one of
- // setjmps in this function. We should not convert this call to an invoke.
- if (CI == WasmLongjmpCI)
- continue;
- LongjmpableCalls.push_back(CI);
- }
- }
- for (auto *CI : LongjmpableCalls) {
- // Even if the callee function has attribute 'nounwind', which is true for
- // all C functions, it can longjmp, which means it can throw a Wasm
- // exception now.
- CI->removeFnAttr(Attribute::NoUnwind);
- if (Function *CalleeF = CI->getCalledFunction())
- CalleeF->removeFnAttr(Attribute::NoUnwind);
- // Change it to an invoke and make it unwind to the catch.dispatch.longjmp
- // BB. If the call is enclosed in another catchpad/cleanuppad scope, unwind
- // to its parent pad's unwind destination instead to preserve the scope
- // structure. It will eventually unwind to the catch.dispatch.longjmp.
- SmallVector<OperandBundleDef, 1> Bundles;
- BasicBlock *UnwindDest = nullptr;
- if (auto Bundle = CI->getOperandBundle(LLVMContext::OB_funclet)) {
- Instruction *FromPad = cast<Instruction>(Bundle->Inputs[0]);
- while (!UnwindDest) {
- if (auto *CPI = dyn_cast<CatchPadInst>(FromPad)) {
- UnwindDest = CPI->getCatchSwitch()->getUnwindDest();
- break;
- }
- if (auto *CPI = dyn_cast<CleanupPadInst>(FromPad)) {
- // getCleanupRetUnwindDest() can return nullptr when
- // 1. This cleanuppad's matching cleanupret uwninds to caller
- // 2. There is no matching cleanupret because it ends with
- // unreachable.
- // In case of 2, we need to traverse the parent pad chain.
- UnwindDest = getCleanupRetUnwindDest(CPI);
- Value *ParentPad = CPI->getParentPad();
- if (isa<ConstantTokenNone>(ParentPad))
- break;
- FromPad = cast<Instruction>(ParentPad);
- }
- }
- }
- if (!UnwindDest)
- UnwindDest = CatchDispatchLongjmpBB;
- changeToInvokeAndSplitBasicBlock(CI, UnwindDest);
- }
- SmallVector<Instruction *, 16> ToErase;
- for (auto &BB : F) {
- if (auto *CSI = dyn_cast<CatchSwitchInst>(BB.getFirstNonPHI())) {
- if (CSI != CatchSwitchLongjmp && CSI->unwindsToCaller()) {
- IRB.SetInsertPoint(CSI);
- ToErase.push_back(CSI);
- auto *NewCSI = IRB.CreateCatchSwitch(CSI->getParentPad(),
- CatchDispatchLongjmpBB, 1);
- NewCSI->addHandler(*CSI->handler_begin());
- NewCSI->takeName(CSI);
- CSI->replaceAllUsesWith(NewCSI);
- }
- }
- if (auto *CRI = dyn_cast<CleanupReturnInst>(BB.getTerminator())) {
- if (CRI->unwindsToCaller()) {
- IRB.SetInsertPoint(CRI);
- ToErase.push_back(CRI);
- IRB.CreateCleanupRet(CRI->getCleanupPad(), CatchDispatchLongjmpBB);
- }
- }
- }
- for (Instruction *I : ToErase)
- I->eraseFromParent();
- }
|