Skip to content

Commit 494fe0b

Browse files
authored
[WebAssembly] Remove wasm-specific findWasmUnwindDestinations (#130374)
Unlike in Itanium EH IR, WinEH IR's unwinding instructions (e.g. `invoke`s) can have multiple possible unwind destinations. For example: ```ll entry: invoke void @foo() to label %cont unwind label %catch.dispatch catch.dispatch: ; preds = %entry %0 = catchswitch within none [label %catch.start] unwind label %terminate catch.start: ; preds = %catch.dispatch %1 = catchpad within %0 [ptr null] ... terminate: ; preds = %catch.dispatch %2 = catchpad within none [] ... ... ``` In this case, if an exception is not caught by `catch.dispatch` (and thus `catch.start`), it should next unwind to `terminate`. `findUnwindDestination` in ISel gathers the list of this unwind destinations traversing the unwind edges: https://github.com/llvm/llvm-project/blob/ae42f071032b29821beef6a33771258086bbbb1c/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp#L2089-L2150 But we don't use that, and instead use our custom `findWasmUnwindDestinations` that only adds the first unwind destination, `catch.start`, to the successor list of `entry`, and not `terminate`: https://github.com/llvm/llvm-project/blob/ae42f071032b29821beef6a33771258086bbbb1c/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp#L2037-L2087 The reason behind it was, as described in the comment block in the code, it was assumed that there always would be an `invoke` that connects `catch.start` and `terminate`. In case of `catch (type)`, there will be `call void @llvm.wasm.rethrow()` in `catch.start`'s predecessor that unwinds to the next destination. For example: https://github.com/llvm/llvm-project/blob/0db702ac8e06911478615ac537f75ac778817c04/llvm/test/CodeGen/WebAssembly/exception.ll#L429-L430 In case of `catch (...)`, `__cxa_end_catch` can throw, so it becomes an `invoke` that unwinds to the next destination. For example: https://github.com/llvm/llvm-project/blob/0db702ac8e06911478615ac537f75ac778817c04/llvm/test/CodeGen/WebAssembly/exception.ll#L537-L538 So the unwind ordering relationship between `catch.start` and `terminate` here would be preserved. But turns out this assumption does not always hold. For example: ```ll entry: invoke void @foo() to label %cont unwind label %catch.dispatch catch.dispatch: ; preds = %entry %0 = catchswitch within none [label %catch.start] unwind label %terminate catch.start: ; preds = %catch.dispatch %1 = catchpad within %0 [ptr null] ... call void @_ZSt9terminatev() unreachable terminate: ; preds = %catch.dispatch %2 = catchpad within none [] call void @_ZSt9terminatev() unreachable ... ``` In this case there is no `invoke` that connects `catch.start` to `terminate`. So after `catch.dispatch` BB is removed in ISel, `terminate` is considered unreachable and incorrectly removed in DCE. This makes Wasm just use the general `findUnwindDestination`. In that case `entry`'s successor is going to be [`catch.start`, `terminate`]. We can get the first unwind destination by just traversing the list from the front. --- This required another change in WinEHPrepare. WinEHPrepare demotes all PHIs in EH pads because they are funclets in Windows and funclets can't have PHIs. When used in Wasm they are not funclets so we don't need to do that wholesale but we still need to demote PHIs in `catchswitch` BBs because they are deleted during ISel. (So we created [`-demote-catchswitch-only`](https://github.com/llvm/llvm-project/blob/a5588b6d20590a10db0f1a2046fba4d9f205ed68/llvm/lib/CodeGen/WinEHPrepare.cpp#L57-L59) option for that) But turns out we need to remove PHIs that have a `catchswitch` BB as an incoming block too: ```ll ... catch.dispatch: %0 = catchswitch within none [label %catch.start] unwind label %terminate catch.start: ... somebb: ... ehcleanup ; preds = %catch.dispatch, %somebb %1 = phi i32 [ 10, %catch.dispatch ], [ 20, %somebb ] ... ``` In this case the `phi` in `ehcleanup` BB should be demoted too because `catch.dispatch` BB will be removed in ISel so one if its incoming block will be gone. This pattern didn't manifest before presumably due to how `findWasmUnwindDestinations` worked. (In this example, in our `findWasmUnwindDestinations`, `catch.dispatch` would have had only one successor, `catch.start`. But now `catch.dispatch` has both `catch.start` and `ehcleanup` as successors, revealing this bug. This case is [represented](https://github.com/llvm/llvm-project/blob/ab87206c4b95aa0b5047facffb5f78f7fe6ac269/llvm/test/CodeGen/WebAssembly/exception.ll#L445) by `rethrow_terminator` function in `exception.ll` (or `exception-legacy.ll`) and without the WinEHPrepare fix it will crash. --- Discovered by the reproducer provided in #126916, even though the bug reported there was not this one.
1 parent 95e38ba commit 494fe0b

File tree

6 files changed

+126
-76
lines changed

6 files changed

+126
-76
lines changed

llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp

Lines changed: 5 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2034,58 +2034,6 @@ void SelectionDAGBuilder::visitCleanupPad(const CleanupPadInst &CPI) {
20342034
}
20352035
}
20362036

2037-
// In wasm EH, even though a catchpad may not catch an exception if a tag does
2038-
// not match, it is OK to add only the first unwind destination catchpad to the
2039-
// successors, because there will be at least one invoke instruction within the
2040-
// catch scope that points to the next unwind destination, if one exists, so
2041-
// CFGSort cannot mess up with BB sorting order.
2042-
// (All catchpads with 'catch (type)' clauses have a 'llvm.rethrow' intrinsic
2043-
// call within them, and catchpads only consisting of 'catch (...)' have a
2044-
// '__cxa_end_catch' call within them, both of which generate invokes in case
2045-
// the next unwind destination exists, i.e., the next unwind destination is not
2046-
// the caller.)
2047-
//
2048-
// Having at most one EH pad successor is also simpler and helps later
2049-
// transformations.
2050-
//
2051-
// For example,
2052-
// current:
2053-
// invoke void @foo to ... unwind label %catch.dispatch
2054-
// catch.dispatch:
2055-
// %0 = catchswitch within ... [label %catch.start] unwind label %next
2056-
// catch.start:
2057-
// ...
2058-
// ... in this BB or some other child BB dominated by this BB there will be an
2059-
// invoke that points to 'next' BB as an unwind destination
2060-
//
2061-
// next: ; We don't need to add this to 'current' BB's successor
2062-
// ...
2063-
static void findWasmUnwindDestinations(
2064-
FunctionLoweringInfo &FuncInfo, const BasicBlock *EHPadBB,
2065-
BranchProbability Prob,
2066-
SmallVectorImpl<std::pair<MachineBasicBlock *, BranchProbability>>
2067-
&UnwindDests) {
2068-
while (EHPadBB) {
2069-
BasicBlock::const_iterator Pad = EHPadBB->getFirstNonPHIIt();
2070-
if (isa<CleanupPadInst>(Pad)) {
2071-
// Stop on cleanup pads.
2072-
UnwindDests.emplace_back(FuncInfo.getMBB(EHPadBB), Prob);
2073-
UnwindDests.back().first->setIsEHScopeEntry();
2074-
break;
2075-
} else if (const auto *CatchSwitch = dyn_cast<CatchSwitchInst>(Pad)) {
2076-
// Add the catchpad handlers to the possible destinations. We don't
2077-
// continue to the unwind destination of the catchswitch for wasm.
2078-
for (const BasicBlock *CatchPadBB : CatchSwitch->handlers()) {
2079-
UnwindDests.emplace_back(FuncInfo.getMBB(CatchPadBB), Prob);
2080-
UnwindDests.back().first->setIsEHScopeEntry();
2081-
}
2082-
break;
2083-
} else {
2084-
continue;
2085-
}
2086-
}
2087-
}
2088-
20892037
/// When an invoke or a cleanupret unwinds to the next EH pad, there are
20902038
/// many places it could ultimately go. In the IR, we have a single unwind
20912039
/// destination, but in the machine CFG, we enumerate all the possible blocks.
@@ -2106,13 +2054,6 @@ static void findUnwindDestinations(
21062054
bool IsWasmCXX = Personality == EHPersonality::Wasm_CXX;
21072055
bool IsSEH = isAsynchronousEHPersonality(Personality);
21082056

2109-
if (IsWasmCXX) {
2110-
findWasmUnwindDestinations(FuncInfo, EHPadBB, Prob, UnwindDests);
2111-
assert(UnwindDests.size() <= 1 &&
2112-
"There should be at most one unwind destination for wasm");
2113-
return;
2114-
}
2115-
21162057
while (EHPadBB) {
21172058
BasicBlock::const_iterator Pad = EHPadBB->getFirstNonPHIIt();
21182059
BasicBlock *NewEHPadBB = nullptr;
@@ -2122,10 +2063,13 @@ static void findUnwindDestinations(
21222063
break;
21232064
} else if (isa<CleanupPadInst>(Pad)) {
21242065
// Stop on cleanup pads. Cleanups are always funclet entries for all known
2125-
// personalities.
2066+
// personalities except Wasm. And in Wasm this becomes a catch_all(_ref),
2067+
// which always catches an exception.
21262068
UnwindDests.emplace_back(FuncInfo.getMBB(EHPadBB), Prob);
21272069
UnwindDests.back().first->setIsEHScopeEntry();
2128-
UnwindDests.back().first->setIsEHFuncletEntry();
2070+
// In Wasm, EH scopes are not funclets
2071+
if (!IsWasmCXX)
2072+
UnwindDests.back().first->setIsEHFuncletEntry();
21292073
break;
21302074
} else if (const auto *CatchSwitch = dyn_cast<CatchSwitchInst>(Pad)) {
21312075
// Add the catchpad handlers to the possible destinations.

llvm/lib/CodeGen/WinEHPrepare.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -866,16 +866,30 @@ void WinEHPrepareImpl::demotePHIsOnFunclets(Function &F,
866866
for (BasicBlock &BB : make_early_inc_range(F)) {
867867
if (!BB.isEHPad())
868868
continue;
869-
if (DemoteCatchSwitchPHIOnly &&
870-
!isa<CatchSwitchInst>(BB.getFirstNonPHIIt()))
871-
continue;
872869

873870
for (Instruction &I : make_early_inc_range(BB)) {
874871
auto *PN = dyn_cast<PHINode>(&I);
875872
// Stop at the first non-PHI.
876873
if (!PN)
877874
break;
878875

876+
// If DemoteCatchSwitchPHIOnly is true, we only demote a PHI when
877+
// 1. The PHI is within a catchswitch BB
878+
// 2. The PHI has a catchswitch BB has one of its incoming blocks
879+
if (DemoteCatchSwitchPHIOnly) {
880+
bool IsCatchSwitchBB = isa<CatchSwitchInst>(BB.getFirstNonPHIIt());
881+
bool HasIncomingCatchSwitchBB = false;
882+
for (unsigned I = 0, E = PN->getNumIncomingValues(); I < E; ++I) {
883+
if (isa<CatchSwitchInst>(
884+
PN->getIncomingBlock(I)->getFirstNonPHIIt())) {
885+
HasIncomingCatchSwitchBB = true;
886+
break;
887+
}
888+
}
889+
if (!IsCatchSwitchBB && !HasIncomingCatchSwitchBB)
890+
break;
891+
}
892+
879893
AllocaInst *SpillSlot = insertPHILoads(PN, F);
880894
if (SpillSlot)
881895
insertPHIStores(PN, SpillSlot);

llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1850,13 +1850,12 @@ bool WebAssemblyCFGStackify::fixCallUnwindMismatches(MachineFunction &MF) {
18501850

18511851
// If the EH pad on the stack top is where this instruction should unwind
18521852
// next, we're good.
1853-
MachineBasicBlock *UnwindDest = getFakeCallerBlock(MF);
1853+
MachineBasicBlock *UnwindDest = nullptr;
18541854
for (auto *Succ : MBB.successors()) {
18551855
// Even though semantically a BB can have multiple successors in case an
1856-
// exception is not caught by a catchpad, in our backend implementation
1857-
// it is guaranteed that a BB can have at most one EH pad successor. For
1858-
// details, refer to comments in findWasmUnwindDestinations function in
1859-
// SelectionDAGBuilder.cpp.
1856+
// exception is not caught by a catchpad, the first unwind destination
1857+
// should appear first in the successor list, based on the calculation
1858+
// in findUnwindDestinations() in SelectionDAGBuilder.cpp.
18601859
if (Succ->isEHPad()) {
18611860
UnwindDest = Succ;
18621861
break;

llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -659,9 +659,6 @@ static bool canLongjmp(const Value *Callee) {
659659
// Every catchpad generated by Wasm C++ contains __cxa_end_catch, so we
660660
// intentionally treat it as longjmpable to work around this problem. This is
661661
// a hacky fix but an easy one.
662-
//
663-
// The comment block in findWasmUnwindDestinations() in
664-
// SelectionDAGBuilder.cpp is addressing a similar problem.
665662
if (CalleeName == "__cxa_end_catch")
666663
return WebAssembly::WasmEnableSjLj;
667664
if (CalleeName == "__cxa_begin_catch" ||

llvm/test/CodeGen/WebAssembly/exception-legacy.ll

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ invoke.cont2: ; preds = %ehcleanup
171171

172172
terminate: ; preds = %ehcleanup
173173
%6 = cleanuppad within %5 []
174-
call void @_ZSt9terminatev() [ "funclet"(token %6) ]
174+
call void @_ZSt9terminatev() #2 [ "funclet"(token %6) ]
175175
unreachable
176176
}
177177

@@ -477,7 +477,7 @@ invoke.cont.i: ; preds = %catch.start.i
477477

478478
terminate.i: ; preds = %catch.start.i, %catch.dispatch.i
479479
%6 = cleanuppad within %0 []
480-
call void @_ZSt9terminatev() [ "funclet"(token %6) ]
480+
call void @_ZSt9terminatev() #2 [ "funclet"(token %6) ]
481481
unreachable
482482

483483
_ZN4TempD2Ev.exit: ; preds = %invoke.cont.i
@@ -501,6 +501,50 @@ unreachable: ; preds = %entry
501501
unreachable
502502
}
503503

504+
; Regression test for a bug where, when an invoke unwinds to a catchswitch, the
505+
; catchswitch's unwind destination was not included in the invoke's unwind
506+
; destination when there was no direct link from catch.start to there.
507+
508+
; CHECK-LABEL: unwind_destinations:
509+
; CHECK: try
510+
; CHECK: try
511+
; CHECK: call foo
512+
; CHECK: catch $0=, __cpp_exception
513+
; CHECK: call _ZSt9terminatev
514+
; CHECK: unreachable
515+
; CHECK: end_try
516+
; Note the below is "terminate" BB and should not be DCE'd
517+
; CHECK: catch_all
518+
; CHECK: call _ZSt9terminatev
519+
; CHECK: unreachable
520+
; CHECK: end_try
521+
; CHECK: return
522+
define void @unwind_destinations() personality ptr @__gxx_wasm_personality_v0 {
523+
entry:
524+
invoke void @foo()
525+
to label %try.cont unwind label %catch.dispatch
526+
527+
catch.dispatch: ; preds = %entry
528+
%0 = catchswitch within none [label %catch.start] unwind label %terminate
529+
530+
catch.start: ; preds = %catch.dispatch
531+
%1 = catchpad within %0 [ptr null]
532+
%2 = call ptr @llvm.wasm.get.exception(token %1)
533+
%3 = call ptr @__cxa_begin_catch(ptr %2) #5 [ "funclet"(token %1) ]
534+
call void @_ZSt9terminatev() #2 [ "funclet"(token %1) ]
535+
unreachable
536+
537+
; Even if there is no link from catch.start to this terminate BB, when there is
538+
; an exception that catch.start does not catch (e.g. a foreign exception), it
539+
; should end up here, so this BB should NOT be DCE'ed
540+
terminate: ; preds = %catch.dispatch
541+
%4 = cleanuppad within none []
542+
call void @_ZSt9terminatev() #2 [ "funclet"(token %4) ]
543+
unreachable
544+
545+
try.cont: ; preds = %entry
546+
ret void
547+
}
504548

505549
declare void @foo()
506550
declare void @bar(ptr)
@@ -527,5 +571,6 @@ declare ptr @_ZN4TempD2Ev(ptr returned)
527571

528572
attributes #0 = { nounwind }
529573
attributes #1 = { noreturn }
574+
attributes #2 = { noreturn nounwind }
530575

531576
; CHECK: __cpp_exception:

llvm/test/CodeGen/WebAssembly/exception.ll

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ invoke.cont2: ; preds = %ehcleanup
207207

208208
terminate: ; preds = %ehcleanup
209209
%6 = cleanuppad within %5 []
210-
call void @_ZSt9terminatev() [ "funclet"(token %6) ]
210+
call void @_ZSt9terminatev() #2 [ "funclet"(token %6) ]
211211
unreachable
212212
}
213213

@@ -542,7 +542,7 @@ invoke.cont.i: ; preds = %catch.start.i
542542

543543
terminate.i: ; preds = %catch.start.i, %catch.dispatch.i
544544
%6 = cleanuppad within %0 []
545-
call void @_ZSt9terminatev() [ "funclet"(token %6) ]
545+
call void @_ZSt9terminatev() #2 [ "funclet"(token %6) ]
546546
unreachable
547547

548548
_ZN4TempD2Ev.exit: ; preds = %invoke.cont.i
@@ -593,6 +593,56 @@ try.cont: ; preds = %catch, %entry
593593
ret void
594594
}
595595

596+
; Regression test for a bug where, when an invoke unwinds to a catchswitch, the
597+
; catchswitch's unwind destination was not included in the invoke's unwind
598+
; destination when there was no direct link from catch.start to there.
599+
600+
; CHECK-LABEL: unwind_destinations:
601+
; CHECK: block
602+
; CHECK: block
603+
; CHECK: try_table (catch_all 0)
604+
; CHECK: block i32
605+
; CHECK: try_table (catch __cpp_exception 0)
606+
; CHECK: call foo
607+
; CHECK: end_try_table
608+
; CHECK: unreachable
609+
; CHECK: end_block
610+
; CHECK: call _ZSt9terminatev
611+
; CHECK: unreachable
612+
; CHECK: end_try_table
613+
; CHECK: unreachable
614+
; CHECK: end_block
615+
; Note the below is "terminate" BB and should not be DCE'd
616+
; CHECK: call _ZSt9terminatev
617+
; CHECK: unreachable
618+
; CHECK: end_block
619+
define void @unwind_destinations() personality ptr @__gxx_wasm_personality_v0 {
620+
entry:
621+
invoke void @foo()
622+
to label %try.cont unwind label %catch.dispatch
623+
624+
catch.dispatch: ; preds = %entry
625+
%0 = catchswitch within none [label %catch.start] unwind label %terminate
626+
627+
catch.start: ; preds = %catch.dispatch
628+
%1 = catchpad within %0 [ptr null]
629+
%2 = call ptr @llvm.wasm.get.exception(token %1)
630+
%3 = call ptr @__cxa_begin_catch(ptr %2) #5 [ "funclet"(token %1) ]
631+
call void @_ZSt9terminatev() #2 [ "funclet"(token %1) ]
632+
unreachable
633+
634+
; Even if there is no link from catch.start to this terminate BB, when there is
635+
; an exception that catch.start does not catch (e.g. a foreign exception), it
636+
; should end up here, so this BB should NOT be DCE'ed
637+
terminate: ; preds = %catch.dispatch
638+
%4 = cleanuppad within none []
639+
call void @_ZSt9terminatev() #2 [ "funclet"(token %4) ]
640+
unreachable
641+
642+
try.cont: ; preds = %entry
643+
ret void
644+
}
645+
596646
declare void @foo()
597647
declare void @bar(ptr)
598648
declare void @take_i32(i32)
@@ -618,5 +668,6 @@ declare ptr @_ZN4TempD2Ev(ptr returned)
618668

619669
attributes #0 = { nounwind }
620670
attributes #1 = { noreturn }
671+
attributes #2 = { noreturn nounwind }
621672

622673
; CHECK: __cpp_exception:

0 commit comments

Comments
 (0)