@@ -611,6 +611,8 @@ static bool canLongjmp(const Value *Callee) {
611
611
return false ;
612
612
StringRef CalleeName = Callee->getName ();
613
613
614
+ // TODO Include more functions or consider checking with mangled prefixes
615
+
614
616
// The reason we include malloc/free here is to exclude the malloc/free
615
617
// calls generated in setjmp prep / cleanup routines.
616
618
if (CalleeName == " setjmp" || CalleeName == " malloc" || CalleeName == " free" )
@@ -627,11 +629,50 @@ static bool canLongjmp(const Value *Callee) {
627
629
return false ;
628
630
629
631
// 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" ||
631
667
CalleeName == " __cxa_allocate_exception" || CalleeName == " __cxa_throw" ||
632
668
CalleeName == " __clang_call_terminate" )
633
669
return false ;
634
670
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
+
635
676
// Otherwise we don't know
636
677
return true ;
637
678
}
@@ -1271,16 +1312,19 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runSjLjOnFunction(Function &F) {
1271
1312
// Setjmp transformation
1272
1313
SmallVector<PHINode *, 4 > SetjmpRetPHIs;
1273
1314
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 ();
1281
1318
if (BB->getParent () != &F) // in other function
1282
1319
continue ;
1283
1320
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
+
1284
1328
// The tail is everything right after the call, and will be reached once
1285
1329
// when setjmp is called, and later when longjmp returns to the setjmp
1286
1330
BasicBlock *Tail = SplitBlock (BB, CI->getNextNode ());
@@ -1596,6 +1640,13 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForEmscriptenSjLj(
1596
1640
I->eraseFromParent ();
1597
1641
}
1598
1642
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
+
1599
1650
// Create a catchpad in which we catch a longjmp's env and val arguments, test
1600
1651
// if the longjmp corresponds to one of setjmps in the current function, and if
1601
1652
// so, jump to the setjmp dispatch BB from which we go to one of post-setjmp
@@ -1747,15 +1798,66 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(
1747
1798
LongjmpableCalls.push_back (CI);
1748
1799
}
1749
1800
}
1801
+
1750
1802
for (auto *CI : LongjmpableCalls) {
1751
1803
// Even if the callee function has attribute 'nounwind', which is true for
1752
1804
// all C functions, it can longjmp, which means it can throw a Wasm
1753
1805
// exception now.
1754
1806
CI->removeFnAttr (Attribute::NoUnwind);
1755
1807
if (Function *CalleeF = CI->getCalledFunction ())
1756
1808
CalleeF->removeFnAttr (Attribute::NoUnwind);
1809
+
1757
1810
// 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
+ }
1760
1859
}
1860
+
1861
+ for (Instruction *I : ToErase)
1862
+ I->eraseFromParent ();
1761
1863
}
0 commit comments