@@ -611,6 +611,8 @@ static bool canLongjmp(const Value *Callee) {
611611 return false ;
612612 StringRef CalleeName = Callee->getName ();
613613
614+ // TODO Include more functions or consider checking with mangled prefixes
615+
614616 // The reason we include malloc/free here is to exclude the malloc/free
615617 // calls generated in setjmp prep / cleanup routines.
616618 if (CalleeName == " setjmp" || CalleeName == " malloc" || CalleeName == " free" )
@@ -627,11 +629,50 @@ static bool canLongjmp(const Value *Callee) {
627629 return false ;
628630
629631 // Exception-catching related functions
630- if (CalleeName == " __cxa_begin_catch" || CalleeName == " __cxa_end_catch" ||
632+ //
633+ // We intentionally excluded __cxa_end_catch here even though it surely cannot
634+ // longjmp, in order to maintain the unwind relationship from all existing
635+ // catchpads (and calls within them) to catch.dispatch.longjmp.
636+ //
637+ // In Wasm EH + Wasm SjLj, we
638+ // 1. Make all catchswitch and cleanuppad that unwind to caller unwind to
639+ // catch.dispatch.longjmp instead
640+ // 2. Convert all longjmpable calls to invokes that unwind to
641+ // catch.dispatch.longjmp
642+ // But catchswitch BBs are removed in isel, so if an EH catchswitch (generated
643+ // from an exception)'s catchpad does not contain any calls that are converted
644+ // into invokes unwinding to catch.dispatch.longjmp, this unwind relationship
645+ // (EH catchswitch BB -> catch.dispatch.longjmp BB) is lost and
646+ // catch.dispatch.longjmp BB can be placed before the EH catchswitch BB in
647+ // CFGSort.
648+ // int ret = setjmp(buf);
649+ // try {
650+ // foo(); // longjmps
651+ // } catch (...) {
652+ // }
653+ // Then in this code, if 'foo' longjmps, it first unwinds to 'catch (...)'
654+ // catchswitch, and is not caught by that catchswitch because it is a longjmp,
655+ // then it should next unwind to catch.dispatch.longjmp BB. But if this 'catch
656+ // (...)' catchswitch -> catch.dispatch.longjmp unwind relationship is lost,
657+ // it will not unwind to catch.dispatch.longjmp, producing an incorrect
658+ // result.
659+ //
660+ // Every catchpad generated by Wasm C++ contains __cxa_end_catch, so we
661+ // intentionally treat it as longjmpable to work around this problem. This is
662+ // a hacky fix but an easy one.
663+ //
664+ // The comment block in findWasmUnwindDestinations() in
665+ // SelectionDAGBuilder.cpp is addressing a similar problem.
666+ if (CalleeName == " __cxa_begin_catch" ||
631667 CalleeName == " __cxa_allocate_exception" || CalleeName == " __cxa_throw" ||
632668 CalleeName == " __clang_call_terminate" )
633669 return false ;
634670
671+ // std::terminate, which is generated when another exception occurs while
672+ // handling an exception, cannot longjmp.
673+ if (CalleeName == " _ZSt9terminatev" )
674+ return false ;
675+
635676 // Otherwise we don't know
636677 return true ;
637678}
@@ -1271,16 +1312,19 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runSjLjOnFunction(Function &F) {
12711312 // Setjmp transformation
12721313 SmallVector<PHINode *, 4 > SetjmpRetPHIs;
12731314 Function *SetjmpF = M.getFunction (" setjmp" );
1274- for (User *U : SetjmpF->users ()) {
1275- auto *CI = dyn_cast<CallInst>(U);
1276- // FIXME 'invoke' to setjmp can happen when we use Wasm EH + Wasm SjLj, but
1277- // we don't support two being used together yet.
1278- if (!CI)
1279- report_fatal_error (" Wasm EH + Wasm SjLj is not fully supported yet" );
1280- BasicBlock *BB = CI->getParent ();
1315+ for (auto *U : make_early_inc_range (SetjmpF->users ())) {
1316+ auto *CB = dyn_cast<CallBase>(U);
1317+ BasicBlock *BB = CB->getParent ();
12811318 if (BB->getParent () != &F) // in other function
12821319 continue ;
12831320
1321+ CallInst *CI = nullptr ;
1322+ // setjmp cannot throw. So if it is an invoke, lower it to a call
1323+ if (auto *II = dyn_cast<InvokeInst>(CB))
1324+ CI = llvm::changeToCall (II);
1325+ else
1326+ CI = cast<CallInst>(CB);
1327+
12841328 // The tail is everything right after the call, and will be reached once
12851329 // when setjmp is called, and later when longjmp returns to the setjmp
12861330 BasicBlock *Tail = SplitBlock (BB, CI->getNextNode ());
@@ -1596,6 +1640,13 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForEmscriptenSjLj(
15961640 I->eraseFromParent ();
15971641}
15981642
1643+ static BasicBlock *getCleanupRetUnwindDest (const CleanupPadInst *CPI) {
1644+ for (const User *U : CPI->users ())
1645+ if (const auto *CRI = dyn_cast<CleanupReturnInst>(U))
1646+ return CRI->getUnwindDest ();
1647+ return nullptr ;
1648+ }
1649+
15991650// Create a catchpad in which we catch a longjmp's env and val arguments, test
16001651// if the longjmp corresponds to one of setjmps in the current function, and if
16011652// so, jump to the setjmp dispatch BB from which we go to one of post-setjmp
@@ -1747,15 +1798,66 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(
17471798 LongjmpableCalls.push_back (CI);
17481799 }
17491800 }
1801+
17501802 for (auto *CI : LongjmpableCalls) {
17511803 // Even if the callee function has attribute 'nounwind', which is true for
17521804 // all C functions, it can longjmp, which means it can throw a Wasm
17531805 // exception now.
17541806 CI->removeFnAttr (Attribute::NoUnwind);
17551807 if (Function *CalleeF = CI->getCalledFunction ())
17561808 CalleeF->removeFnAttr (Attribute::NoUnwind);
1809+
17571810 // Change it to an invoke and make it unwind to the catch.dispatch.longjmp
1758- // BB.
1759- changeToInvokeAndSplitBasicBlock (CI, CatchDispatchLongjmpBB);
1811+ // BB. If the call is enclosed in another catchpad/cleanuppad scope, unwind
1812+ // to its parent pad's unwind destination instead to preserve the scope
1813+ // structure. It will eventually unwind to the catch.dispatch.longjmp.
1814+ SmallVector<OperandBundleDef, 1 > Bundles;
1815+ BasicBlock *UnwindDest = nullptr ;
1816+ if (auto Bundle = CI->getOperandBundle (LLVMContext::OB_funclet)) {
1817+ Instruction *FromPad = cast<Instruction>(Bundle->Inputs [0 ]);
1818+ while (!UnwindDest && FromPad) {
1819+ if (auto *CPI = dyn_cast<CatchPadInst>(FromPad)) {
1820+ UnwindDest = CPI->getCatchSwitch ()->getUnwindDest ();
1821+ FromPad = nullptr ; // stop searching
1822+ } else if (auto *CPI = dyn_cast<CleanupPadInst>(FromPad)) {
1823+ // getCleanupRetUnwindDest() can return nullptr when
1824+ // 1. This cleanuppad's matching cleanupret uwninds to caller
1825+ // 2. There is no matching cleanupret because it ends with
1826+ // unreachable.
1827+ // In case of 2, we need to traverse the parent pad chain.
1828+ UnwindDest = getCleanupRetUnwindDest (CPI);
1829+ FromPad = cast<Instruction>(CPI->getParentPad ());
1830+ }
1831+ }
1832+ }
1833+ if (!UnwindDest)
1834+ UnwindDest = CatchDispatchLongjmpBB;
1835+ changeToInvokeAndSplitBasicBlock (CI, UnwindDest);
1836+ }
1837+
1838+ SmallVector<Instruction *, 16 > ToErase;
1839+ for (auto &BB : F) {
1840+ if (auto *CSI = dyn_cast<CatchSwitchInst>(BB.getFirstNonPHI ())) {
1841+ if (CSI != CatchSwitchLongjmp && CSI->unwindsToCaller ()) {
1842+ IRB.SetInsertPoint (CSI);
1843+ ToErase.push_back (CSI);
1844+ auto *NewCSI = IRB.CreateCatchSwitch (CSI->getParentPad (),
1845+ CatchDispatchLongjmpBB, 1 );
1846+ NewCSI->addHandler (*CSI->handler_begin ());
1847+ NewCSI->takeName (CSI);
1848+ CSI->replaceAllUsesWith (NewCSI);
1849+ }
1850+ }
1851+
1852+ if (auto *CRI = dyn_cast<CleanupReturnInst>(BB.getTerminator ())) {
1853+ if (CRI->unwindsToCaller ()) {
1854+ IRB.SetInsertPoint (CRI);
1855+ ToErase.push_back (CRI);
1856+ IRB.CreateCleanupRet (CRI->getCleanupPad (), CatchDispatchLongjmpBB);
1857+ }
1858+ }
17601859 }
1860+
1861+ for (Instruction *I : ToErase)
1862+ I->eraseFromParent ();
17611863}
0 commit comments