Skip to content

Commit eb675e9

Browse files
committed
[WebAssembly] Support Wasm EH + Wasm SjLj
D108960 added support for SjLj using Wasm EH instructions, which we call Wasm SjLj going forward. (We call the old SjLj Emscripten SjLj) But it did not support using Wasm EH and Wasm SjLj together. So far users of Wasm EH had to use Wasm EH with Emscripten SjLj, which had a certain limitation and it suffered from bigger code size increases as well. This enables using Wasm EH and Wasm SjLj together. 1. This redirects `catchswitch` and `cleanupret` that unwind to caller to `catch.dispatch.longjmp` BB, which is a `catchswitch` BB that handles longjmps. 2. D108960 converted all longjmpable `call`s to `invokes` that unwind to `catch.dispatch.longjmp`. This CL checks if the `call` is embedded within another `catchpad`, and if so, makes it unwind to its nearest parent's unwind destination, rather than `catch.dispatch.longjmp`. This is necessary to preserve the scoping structure. Reviewed By: dschuff Differential Revision: https://reviews.llvm.org/D117610
1 parent 03909c4 commit eb675e9

File tree

4 files changed

+375
-13
lines changed

4 files changed

+375
-13
lines changed

llvm/include/llvm/Transforms/Utils/Local.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ inline Align getKnownAlignment(Value *V, const DataLayout &DL,
241241
CallInst *createCallMatchingInvoke(InvokeInst *II);
242242

243243
/// This function converts the specified invoek into a normall call.
244-
void changeToCall(InvokeInst *II, DomTreeUpdater *DTU = nullptr);
244+
CallInst *changeToCall(InvokeInst *II, DomTreeUpdater *DTU = nullptr);
245245

246246
///===---------------------------------------------------------------------===//
247247
/// Dbg Intrinsic utilities

llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp

Lines changed: 112 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

llvm/lib/Transforms/Utils/Local.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2189,8 +2189,8 @@ CallInst *llvm::createCallMatchingInvoke(InvokeInst *II) {
21892189
return NewCall;
21902190
}
21912191

2192-
/// changeToCall - Convert the specified invoke into a normal call.
2193-
void llvm::changeToCall(InvokeInst *II, DomTreeUpdater *DTU) {
2192+
// changeToCall - Convert the specified invoke into a normal call.
2193+
CallInst *llvm::changeToCall(InvokeInst *II, DomTreeUpdater *DTU) {
21942194
CallInst *NewCall = createCallMatchingInvoke(II);
21952195
NewCall->takeName(II);
21962196
NewCall->insertBefore(II);
@@ -2207,6 +2207,7 @@ void llvm::changeToCall(InvokeInst *II, DomTreeUpdater *DTU) {
22072207
II->eraseFromParent();
22082208
if (DTU)
22092209
DTU->applyUpdates({{DominatorTree::Delete, BB, UnwindDestBB}});
2210+
return NewCall;
22102211
}
22112212

22122213
BasicBlock *llvm::changeToInvokeAndSplitBasicBlock(CallInst *CI,

0 commit comments

Comments
 (0)