//===----------------------------------------------------------------------===//
#include "llvm/CodeGen/Passes.h"
+#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/Analysis/CFG.h"
#include "llvm/Analysis/EHPersonalities.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
calculateStateNumbersForInvokes(Fn, FuncInfo);
}
-static int addClrEHHandler(WinEHFuncInfo &FuncInfo, int ParentState,
- ClrHandlerType HandlerType, uint32_t TypeToken,
- const BasicBlock *Handler) {
+static int addClrEHHandler(WinEHFuncInfo &FuncInfo, int HandlerParentState,
+ int TryParentState, ClrHandlerType HandlerType,
+ uint32_t TypeToken, const BasicBlock *Handler) {
ClrEHUnwindMapEntry Entry;
- Entry.Parent = ParentState;
+ Entry.HandlerParentState = HandlerParentState;
+ Entry.TryParentState = TryParentState;
Entry.Handler = Handler;
Entry.HandlerType = HandlerType;
Entry.TypeToken = TypeToken;
if (!FuncInfo.EHPadStateMap.empty())
return;
+ // This numbering assigns one state number to each catchpad and cleanuppad.
+ // It also computes two tree-like relations over states:
+ // 1) Each state has a "HandlerParentState", which is the state of the next
+ // outer handler enclosing this state's handler (same as nearest ancestor
+ // per the ParentPad linkage on EH pads, but skipping over catchswitches).
+ // 2) Each state has a "TryParentState", which:
+ // a) for a catchpad that's not the last handler on its catchswitch, is
+ // the state of the next catchpad on that catchswitch
+ // b) for all other pads, is the state of the pad whose try region is the
+ // next outer try region enclosing this state's try region. The "try
+ // regions are not present as such in the IR, but will be inferred
+ // based on the placement of invokes and pads which reach each other
+ // by exceptional exits
+ // Catchswitches do not get their own states, but each gets mapped to the
+ // state of its first catchpad.
+
+ // Step one: walk down from outermost to innermost funclets, assigning each
+ // catchpad and cleanuppad a state number. Add an entry to the
+ // ClrEHUnwindMap for each state, recording its HandlerParentState and
+ // handler attributes. Record the TryParentState as well for each catchpad
+ // that's not the last on its catchswitch, but initialize all other entries'
+ // TryParentStates to a sentinel -1 value that the next pass will update.
+
+ // Seed a worklist with pads that have no parent.
SmallVector<std::pair<const Instruction *, int>, 8> Worklist;
-
- // Each pad needs to be able to refer to its parent, so scan the function
- // looking for top-level handlers and seed the worklist with them.
for (const BasicBlock &BB : *Fn) {
- if (!BB.isEHPad())
- continue;
- if (BB.isLandingPad())
- report_fatal_error("CoreCLR EH cannot use landingpads");
const Instruction *FirstNonPHI = BB.getFirstNonPHI();
- if (!isTopLevelPadForMSVC(FirstNonPHI))
+ const Value *ParentPad;
+ if (const auto *CPI = dyn_cast<CleanupPadInst>(FirstNonPHI))
+ ParentPad = CPI->getParentPad();
+ else if (const auto *CSI = dyn_cast<CatchSwitchInst>(FirstNonPHI))
+ ParentPad = CSI->getParentPad();
+ else
continue;
- // queue this with sentinel parent state -1 to mean unwind to caller.
- Worklist.emplace_back(FirstNonPHI, -1);
+ if (isa<ConstantTokenNone>(ParentPad))
+ Worklist.emplace_back(FirstNonPHI, -1);
}
+ // Use the worklist to visit all pads, from outer to inner. Record
+ // HandlerParentState for all pads. Record TryParentState only for catchpads
+ // that aren't the last on their catchswitch (setting all other entries'
+ // TryParentStates to an initial value of -1). This loop is also responsible
+ // for setting the EHPadStateMap entry for all catchpads, cleanuppads, and
+ // catchswitches.
while (!Worklist.empty()) {
const Instruction *Pad;
- int ParentState;
- std::tie(Pad, ParentState) = Worklist.pop_back_val();
-
- Value *ParentPad;
- int PredState;
- if (const CleanupPadInst *Cleanup = dyn_cast<CleanupPadInst>(Pad)) {
- // A cleanup can have multiple exits; don't re-process after the first.
- if (FuncInfo.EHPadStateMap.count(Cleanup))
- continue;
- // CoreCLR personality uses arity to distinguish faults from finallies.
- const BasicBlock *PadBlock = Cleanup->getParent();
+ int HandlerParentState;
+ std::tie(Pad, HandlerParentState) = Worklist.pop_back_val();
+
+ if (const auto *Cleanup = dyn_cast<CleanupPadInst>(Pad)) {
+ // Create the entry for this cleanup with the appropriate handler
+ // properties. Finaly and fault handlers are distinguished by arity.
ClrHandlerType HandlerType =
- (Cleanup->getNumOperands() ? ClrHandlerType::Fault
- : ClrHandlerType::Finally);
- int NewState =
- addClrEHHandler(FuncInfo, ParentState, HandlerType, 0, PadBlock);
- FuncInfo.EHPadStateMap[Cleanup] = NewState;
- // Propagate the new state to all preds of the cleanup
- ParentPad = Cleanup->getParentPad();
- PredState = NewState;
- } else if (const auto *CatchSwitch = dyn_cast<CatchSwitchInst>(Pad)) {
- SmallVector<const CatchPadInst *, 1> Handlers;
- for (const BasicBlock *CatchPadBB : CatchSwitch->handlers()) {
- const auto *Catch = cast<CatchPadInst>(CatchPadBB->getFirstNonPHI());
- Handlers.push_back(Catch);
- }
- FuncInfo.EHPadStateMap[CatchSwitch] = ParentState;
- int NewState = ParentState;
- for (auto HandlerI = Handlers.rbegin(), HandlerE = Handlers.rend();
- HandlerI != HandlerE; ++HandlerI) {
- const CatchPadInst *Catch = *HandlerI;
- const BasicBlock *PadBlock = Catch->getParent();
+ (Cleanup->getNumArgOperands() ? ClrHandlerType::Fault
+ : ClrHandlerType::Finally);
+ int CleanupState = addClrEHHandler(FuncInfo, HandlerParentState, -1,
+ HandlerType, 0, Pad->getParent());
+ // Queue any child EH pads on the worklist.
+ for (const User *U : Cleanup->users())
+ if (const auto *I = dyn_cast<Instruction>(U))
+ if (I->isEHPad())
+ Worklist.emplace_back(I, CleanupState);
+ // Remember this pad's state.
+ FuncInfo.EHPadStateMap[Cleanup] = CleanupState;
+ } else {
+ // Walk the handlers of this catchswitch in reverse order since all but
+ // the last need to set the following one as its TryParentState.
+ const auto *CatchSwitch = cast<CatchSwitchInst>(Pad);
+ int CatchState = -1, FollowerState = -1;
+ SmallVector<const BasicBlock *, 4> CatchBlocks(CatchSwitch->handlers());
+ for (auto CBI = CatchBlocks.rbegin(), CBE = CatchBlocks.rend();
+ CBI != CBE; ++CBI, FollowerState = CatchState) {
+ const BasicBlock *CatchBlock = *CBI;
+ // Create the entry for this catch with the appropriate handler
+ // properties.
+ const auto *Catch = cast<CatchPadInst>(CatchBlock->getFirstNonPHI());
uint32_t TypeToken = static_cast<uint32_t>(
cast<ConstantInt>(Catch->getArgOperand(0))->getZExtValue());
- NewState = addClrEHHandler(FuncInfo, NewState, ClrHandlerType::Catch,
- TypeToken, PadBlock);
- FuncInfo.EHPadStateMap[Catch] = NewState;
+ CatchState =
+ addClrEHHandler(FuncInfo, HandlerParentState, FollowerState,
+ ClrHandlerType::Catch, TypeToken, CatchBlock);
+ // Queue any child EH pads on the worklist.
+ for (const User *U : Catch->users())
+ if (const auto *I = dyn_cast<Instruction>(U))
+ if (I->isEHPad())
+ Worklist.emplace_back(I, CatchState);
+ // Remember this catch's state.
+ FuncInfo.EHPadStateMap[Catch] = CatchState;
}
- for (const auto *CatchPad : Handlers) {
- for (const User *U : CatchPad->users()) {
- const auto *UserI = cast<Instruction>(U);
- if (UserI->isEHPad())
- Worklist.emplace_back(UserI, ParentState);
+ // Associate the catchswitch with the state of its first catch.
+ assert(CatchSwitch->getNumHandlers());
+ FuncInfo.EHPadStateMap[CatchSwitch] = CatchState;
+ }
+ }
+
+ // Step two: record the TryParentState of each state. For cleanuppads that
+ // don't have cleanuprets, we may need to infer this from their child pads,
+ // so visit pads in descendant-most to ancestor-most order.
+ for (auto Entry = FuncInfo.ClrEHUnwindMap.rbegin(),
+ End = FuncInfo.ClrEHUnwindMap.rend();
+ Entry != End; ++Entry) {
+ const Instruction *Pad =
+ Entry->Handler.get<const BasicBlock *>()->getFirstNonPHI();
+ // For most pads, the TryParentState is the state associated with the
+ // unwind dest of exceptional exits from it.
+ const BasicBlock *UnwindDest;
+ if (const auto *Catch = dyn_cast<CatchPadInst>(Pad)) {
+ // If a catch is not the last in its catchswitch, its TryParentState is
+ // the state associated with the next catch in the switch, even though
+ // that's not the unwind dest of exceptions escaping the catch. Those
+ // cases were already assigned a TryParentState in the first pass, so
+ // skip them.
+ if (Entry->TryParentState != -1)
+ continue;
+ // Otherwise, get the unwind dest from the catchswitch.
+ UnwindDest = Catch->getCatchSwitch()->getUnwindDest();
+ } else {
+ const auto *Cleanup = cast<CleanupPadInst>(Pad);
+ UnwindDest = nullptr;
+ for (const User *U : Cleanup->users()) {
+ if (auto *CleanupRet = dyn_cast<CleanupReturnInst>(U)) {
+ // Common and unambiguous case -- cleanupret indicates cleanup's
+ // unwind dest.
+ UnwindDest = CleanupRet->getUnwindDest();
+ break;
+ }
+
+ // Get an unwind dest for the user
+ const BasicBlock *UserUnwindDest = nullptr;
+ if (auto *Invoke = dyn_cast<InvokeInst>(U)) {
+ UserUnwindDest = Invoke->getUnwindDest();
+ } else if (auto *CatchSwitch = dyn_cast<CatchSwitchInst>(U)) {
+ UserUnwindDest = CatchSwitch->getUnwindDest();
+ } else if (auto *ChildCleanup = dyn_cast<CleanupPadInst>(U)) {
+ int UserState = FuncInfo.EHPadStateMap[ChildCleanup];
+ int UserUnwindState =
+ FuncInfo.ClrEHUnwindMap[UserState].TryParentState;
+ if (UserUnwindState != -1)
+ UserUnwindDest = FuncInfo.ClrEHUnwindMap[UserUnwindState]
+ .Handler.get<const BasicBlock *>();
}
+
+ // Not having an unwind dest for this user might indicate that it
+ // doesn't unwind, so can't be taken as proof that the cleanup itself
+ // may unwind to caller (see e.g. SimplifyUnreachable and
+ // RemoveUnwindEdge).
+ if (!UserUnwindDest)
+ continue;
+
+ // Now we have an unwind dest for the user, but we need to see if it
+ // unwinds all the way out of the cleanup or if it stays within it.
+ const Instruction *UserUnwindPad = UserUnwindDest->getFirstNonPHI();
+ const Value *UserUnwindParent;
+ if (auto *CSI = dyn_cast<CatchSwitchInst>(UserUnwindPad))
+ UserUnwindParent = CSI->getParentPad();
+ else
+ UserUnwindParent =
+ cast<CleanupPadInst>(UserUnwindPad)->getParentPad();
+
+ // The unwind stays within the cleanup iff it targets a child of the
+ // cleanup.
+ if (UserUnwindParent == Cleanup)
+ continue;
+
+ // This unwind exits the cleanup, so its dest is the cleanup's dest.
+ UnwindDest = UserUnwindDest;
+ break;
}
- PredState = NewState;
- ParentPad = CatchSwitch->getParentPad();
- } else {
- llvm_unreachable("Unexpected EH pad");
}
- // Queue all predecessors with the given state
- for (const BasicBlock *Pred : predecessors(Pad->getParent())) {
- if ((Pred = getEHPadFromPredecessor(Pred, ParentPad)))
- Worklist.emplace_back(Pred->getFirstNonPHI(), PredState);
+ // Record the state of the unwind dest as the TryParentState.
+ int UnwindDestState;
+
+ // If UnwindDest is null at this point, either the pad in question can
+ // be exited by unwind to caller, or it cannot be exited by unwind. In
+ // either case, reporting such cases as unwinding to caller is correct.
+ // This can lead to EH tables that "look strange" -- if this pad's is in
+ // a parent funclet which has other children that do unwind to an enclosing
+ // pad, the try region for this pad will be missing the "duplicate" EH
+ // clause entries that you'd expect to see covering the whole parent. That
+ // should be benign, since the unwind never actually happens. If it were
+ // an issue, we could add a subsequent pass that pushes unwind dests down
+ // from parents that have them to children that appear to unwind to caller.
+ if (!UnwindDest) {
+ UnwindDestState = -1;
+ } else {
+ UnwindDestState = FuncInfo.EHPadStateMap[UnwindDest->getFirstNonPHI()];
}
+
+ Entry->TryParentState = UnwindDestState;
}
+ // Step three: transfer information from pads to invokes.
calculateStateNumbersForInvokes(Fn, FuncInfo);
}
; }
; f(8);
; }
-
+;
; CHECK-LABEL: test1: # @test1
-; CHECK-NEXT: [[L_begin:.*func_begin.*]]:
+; CHECK-NEXT: [[test1_begin:.*func_begin.*]]:
define void @test1() personality i8* bitcast (void ()* @ProcessCLRException to i8*) {
entry:
; CHECK: # %entry
; CHECK: leaq [[FPOffset:[0-9]+]](%rsp), %rbp
; CHECK: .seh_endprologue
; CHECK: movq %rsp, [[PSPSymOffset:[0-9]+]](%rsp)
-; CHECK: [[L_before_f1:.+]]:
+; CHECK: [[test1_before_f1:.+]]:
; CHECK-NEXT: movl $1, %ecx
; CHECK-NEXT: callq f
-; CHECK-NEXT: [[L_after_f1:.+]]:
+; CHECK-NEXT: [[test1_after_f1:.+]]:
invoke void @f(i32 1)
- to label %inner_try unwind label %finally.pad
+ to label %inner_try unwind label %finally
inner_try:
; CHECK: # %inner_try
-; CHECK: [[L_before_f2:.+]]:
+; CHECK: [[test1_before_f2:.+]]:
; CHECK-NEXT: movl $2, %ecx
; CHECK-NEXT: callq f
-; CHECK-NEXT: [[L_after_f2:.+]]:
+; CHECK-NEXT: [[test1_after_f2:.+]]:
invoke void @f(i32 2)
- to label %finally.clone unwind label %catch1.pad
-catch1.pad:
- %cs1 = catchswitch within none [label %catch1.body, label %catch2.body] unwind label %finally.pad
-catch1.body:
- %catch1 = catchpad within %cs1 [i32 1]
-; CHECK: .seh_proc [[L_catch1:[^ ]+]]
+ to label %finally.clone unwind label %exn.dispatch
+exn.dispatch:
+ %catchswitch = catchswitch within none [label %catch1, label %catch2] unwind label %finally
+catch1:
+ %catch.pad1 = catchpad within %catchswitch [i32 1]
+; CHECK: .seh_proc [[test1_catch1:[^ ]+]]
; CHECK: .seh_stackalloc [[FuncletFrameSize:[0-9]+]]
; ^ all funclets use the same frame size
; CHECK: movq [[PSPSymOffset]](%rcx), %rcx
; CHECK: movq %rdx, %rcx
; ^ exception pointer passed in rdx
; CHECK-NEXT: callq g
- %exn1 = call i8 addrspace(1)* @llvm.eh.exceptionpointer.p1i8(token %catch1)
- call void @g(i8 addrspace(1)* %exn1) [ "funclet"(token %catch1) ]
-; CHECK: [[L_before_f3:.+]]:
+ %exn1 = call i8 addrspace(1)* @llvm.eh.exceptionpointer.p1i8(token %catch.pad1)
+ call void @g(i8 addrspace(1)* %exn1) [ "funclet"(token %catch.pad1) ]
+; CHECK: [[test1_before_f3:.+]]:
; CHECK-NEXT: movl $3, %ecx
; CHECK-NEXT: callq f
-; CHECK-NEXT: [[L_after_f3:.+]]:
- invoke void @f(i32 3) [ "funclet"(token %catch1) ]
- to label %catch1.ret unwind label %finally.pad
+; CHECK-NEXT: [[test1_after_f3:.+]]:
+ invoke void @f(i32 3) [ "funclet"(token %catch.pad1) ]
+ to label %catch1.ret unwind label %finally
catch1.ret:
- catchret from %catch1 to label %finally.clone
-catch2.body:
- %catch2 = catchpad within %cs1 [i32 2]
-; CHECK: .seh_proc [[L_catch2:[^ ]+]]
+ catchret from %catch.pad1 to label %finally.clone
+catch2:
+ %catch.pad2 = catchpad within %catchswitch [i32 2]
+; CHECK: .seh_proc [[test1_catch2:[^ ]+]]
; CHECK: .seh_stackalloc [[FuncletFrameSize:[0-9]+]]
; ^ all funclets use the same frame size
; CHECK: movq [[PSPSymOffset]](%rcx), %rcx
; CHECK: movq %rdx, %rcx
; ^ exception pointer passed in rdx
; CHECK-NEXT: callq g
- %exn2 = call i8 addrspace(1)* @llvm.eh.exceptionpointer.p1i8(token %catch2)
- call void @g(i8 addrspace(1)* %exn2) [ "funclet"(token %catch2) ]
-; CHECK: [[L_before_f4:.+]]:
+ %exn2 = call i8 addrspace(1)* @llvm.eh.exceptionpointer.p1i8(token %catch.pad2)
+ call void @g(i8 addrspace(1)* %exn2) [ "funclet"(token %catch.pad2) ]
+; CHECK: [[test1_before_f4:.+]]:
; CHECK-NEXT: movl $4, %ecx
; CHECK-NEXT: callq f
-; CHECK-NEXT: [[L_after_f4:.+]]:
- invoke void @f(i32 4) [ "funclet"(token %catch2) ]
- to label %try_in_catch unwind label %finally.pad
+; CHECK-NEXT: [[test1_after_f4:.+]]:
+ invoke void @f(i32 4) [ "funclet"(token %catch.pad2) ]
+ to label %try_in_catch unwind label %finally
try_in_catch:
; CHECK: # %try_in_catch
-; CHECK: [[L_before_f5:.+]]:
+; CHECK: [[test1_before_f5:.+]]:
; CHECK-NEXT: movl $5, %ecx
; CHECK-NEXT: callq f
-; CHECK-NEXT: [[L_after_f5:.+]]:
- invoke void @f(i32 5) [ "funclet"(token %catch2) ]
- to label %catch2.ret unwind label %fault.pad
-fault.pad:
-; CHECK: .seh_proc [[L_fault:[^ ]+]]
- %fault = cleanuppad within none [i32 undef]
+; CHECK-NEXT: [[test1_after_f5:.+]]:
+ invoke void @f(i32 5) [ "funclet"(token %catch.pad2) ]
+ to label %catch2.ret unwind label %fault
+fault:
+; CHECK: .seh_proc [[test1_fault:[^ ]+]]
+ %fault.pad = cleanuppad within %catch.pad2 [i32 undef]
; CHECK: .seh_stackalloc [[FuncletFrameSize:[0-9]+]]
; ^ all funclets use the same frame size
; CHECK: movq [[PSPSymOffset]](%rcx), %rcx
; CHECK: movq %rcx, [[PSPSymOffset]](%rsp)
; CHECK: leaq [[FPOffset]](%rcx), %rbp
; CHECK: .seh_endprologue
-; CHECK: [[L_before_f6:.+]]:
+; CHECK: [[test1_before_f6:.+]]:
; CHECK-NEXT: movl $6, %ecx
; CHECK-NEXT: callq f
-; CHECK-NEXT: [[L_after_f6:.+]]:
- invoke void @f(i32 6) [ "funclet"(token %fault) ]
- to label %fault.ret unwind label %finally.pad
+; CHECK-NEXT: [[test1_after_f6:.+]]:
+ invoke void @f(i32 6) [ "funclet"(token %fault.pad) ]
+ to label %fault.ret unwind label %finally
fault.ret:
- cleanupret from %fault unwind label %finally.pad
+ cleanupret from %fault.pad unwind label %finally
catch2.ret:
- catchret from %catch2 to label %finally.clone
+ catchret from %catch.pad2 to label %finally.clone
finally.clone:
call void @f(i32 7)
br label %tail
-finally.pad:
-; CHECK: .seh_proc [[L_finally:[^ ]+]]
- %finally = cleanuppad within none []
+finally:
+; CHECK: .seh_proc [[test1_finally:[^ ]+]]
+ %finally.pad = cleanuppad within none []
; CHECK: .seh_stackalloc [[FuncletFrameSize:[0-9]+]]
; ^ all funclets use the same frame size
; CHECK: movq [[PSPSymOffset]](%rcx), %rcx
; CHECK: .seh_endprologue
; CHECK-NEXT: movl $7, %ecx
; CHECK-NEXT: callq f
- call void @f(i32 7) [ "funclet"(token %finally) ]
- cleanupret from %finally unwind to caller
+ call void @f(i32 7) [ "funclet"(token %finally.pad) ]
+ cleanupret from %finally.pad unwind to caller
tail:
call void @f(i32 8)
ret void
-; CHECK: [[L_end:.*func_end.*]]:
+; CHECK: [[test1_end:.*func_end.*]]:
}
-; FIXME: Verify that the new clauses are correct and re-enable these checks.
-
; Now check for EH table in xdata (following standard xdata)
-; CHECKX-LABEL: .section .xdata
+; CHECK-LABEL: .section .xdata
; standard xdata comes here
-; CHECKX: .long 4{{$}}
+; CHECK: .long 4{{$}}
; ^ number of funclets
-; CHECKX-NEXT: .long [[L_catch1]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_catch1]]-[[test1_begin]]
; ^ offset from L_begin to start of 1st funclet
-; CHECKX-NEXT: .long [[L_catch2]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_catch2]]-[[test1_begin]]
; ^ offset from L_begin to start of 2nd funclet
-; CHECKX-NEXT: .long [[L_fault]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_fault]]-[[test1_begin]]
; ^ offset from L_begin to start of 3rd funclet
-; CHECKX-NEXT: .long [[L_finally]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_finally]]-[[test1_begin]]
; ^ offset from L_begin to start of 4th funclet
-; CHECKX-NEXT: .long [[L_end]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_end]]-[[test1_begin]]
; ^ offset from L_begin to end of last funclet
-; CHECKX-NEXT: .long 7
+; CHECK-NEXT: .long 7
; ^ number of EH clauses
; Clause 1: call f(2) is guarded by catch1
-; CHECKX-NEXT: .long 0
+; CHECK-NEXT: .long 0
; ^ flags (0 => catch handler)
-; CHECKX-NEXT: .long ([[L_before_f2]]-[[L_begin]])+1
+; CHECK-NEXT: .long ([[test1_before_f2]]-[[test1_begin]])+1
; ^ offset of start of clause
-; CHECKX-NEXT: .long ([[L_after_f2]]-[[L_begin]])+1
+; CHECK-NEXT: .long ([[test1_after_f2]]-[[test1_begin]])+1
; ^ offset of end of clause
-; CHECKX-NEXT: .long [[L_catch1]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_catch1]]-[[test1_begin]]
; ^ offset of start of handler
-; CHECKX-NEXT: .long [[L_catch2]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_catch2]]-[[test1_begin]]
; ^ offset of end of handler
-; CHECKX-NEXT: .long 1
+; CHECK-NEXT: .long 1
; ^ type token of catch (from catchpad)
; Clause 2: call f(2) is also guarded by catch2
-; CHECKX-NEXT: .long 0
+; CHECK-NEXT: .long 0
; ^ flags (0 => catch handler)
-; CHECKX-NEXT: .long ([[L_before_f2]]-[[L_begin]])+1
+; CHECK-NEXT: .long ([[test1_before_f2]]-[[test1_begin]])+1
; ^ offset of start of clause
-; CHECKX-NEXT: .long ([[L_after_f2]]-[[L_begin]])+1
+; CHECK-NEXT: .long ([[test1_after_f2]]-[[test1_begin]])+1
; ^ offset of end of clause
-; CHECKX-NEXT: .long [[L_catch2]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_catch2]]-[[test1_begin]]
; ^ offset of start of handler
-; CHECKX-NEXT: .long [[L_fault]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_fault]]-[[test1_begin]]
; ^ offset of end of handler
-; CHECKX-NEXT: .long 2
+; CHECK-NEXT: .long 2
; ^ type token of catch (from catchpad)
; Clause 3: calls f(1) and f(2) are guarded by finally
-; CHECKX-NEXT: .long 2
+; CHECK-NEXT: .long 2
; ^ flags (2 => finally handler)
-; CHECKX-NEXT: .long ([[L_before_f1]]-[[L_begin]])+1
+; CHECK-NEXT: .long ([[test1_before_f1]]-[[test1_begin]])+1
; ^ offset of start of clause
-; CHECKX-NEXT: .long ([[L_after_f2]]-[[L_begin]])+1
+; CHECK-NEXT: .long ([[test1_after_f2]]-[[test1_begin]])+1
; ^ offset of end of clause
-; CHECKX-NEXT: .long [[L_finally]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_finally]]-[[test1_begin]]
; ^ offset of start of handler
-; CHECKX-NEXT: .long [[L_end]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_end]]-[[test1_begin]]
; ^ offset of end of handler
-; CHECKX-NEXT: .long 0
+; CHECK-NEXT: .long 0
; ^ type token slot (null for finally)
; Clause 4: call f(3) is guarded by finally
; This is a "duplicate" because the protected range (f(3))
; is in funclet catch1 but the finally's immediate parent
; is the main function, not that funclet.
-; CHECKX-NEXT: .long 10
+; CHECK-NEXT: .long 10
; ^ flags (2 => finally handler | 8 => duplicate)
-; CHECKX-NEXT: .long ([[L_before_f3]]-[[L_begin]])+1
+; CHECK-NEXT: .long ([[test1_before_f3]]-[[test1_begin]])+1
; ^ offset of start of clause
-; CHECKX-NEXT: .long ([[L_after_f3]]-[[L_begin]])+1
+; CHECK-NEXT: .long ([[test1_after_f3]]-[[test1_begin]])+1
; ^ offset of end of clause
-; CHECKX-NEXT: .long [[L_finally]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_finally]]-[[test1_begin]]
; ^ offset of start of handler
-; CHECKX-NEXT: .long [[L_end]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_end]]-[[test1_begin]]
; ^ offset of end of handler
-; CHECKX-NEXT: .long 0
+; CHECK-NEXT: .long 0
; ^ type token slot (null for finally)
; Clause 5: call f(5) is guarded by fault
-; CHECKX-NEXT: .long 4
+; CHECK-NEXT: .long 4
; ^ flags (4 => fault handler)
-; CHECKX-NEXT: .long ([[L_before_f5]]-[[L_begin]])+1
+; CHECK-NEXT: .long ([[test1_before_f5]]-[[test1_begin]])+1
; ^ offset of start of clause
-; CHECKX-NEXT: .long ([[L_after_f5]]-[[L_begin]])+1
+; CHECK-NEXT: .long ([[test1_after_f5]]-[[test1_begin]])+1
; ^ offset of end of clause
-; CHECKX-NEXT: .long [[L_fault]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_fault]]-[[test1_begin]]
; ^ offset of start of handler
-; CHECKX-NEXT: .long [[L_finally]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_finally]]-[[test1_begin]]
; ^ offset of end of handler
-; CHECKX-NEXT: .long 0
+; CHECK-NEXT: .long 0
; ^ type token slot (null for fault)
; Clause 6: calls f(4) and f(5) are guarded by finally
; This is a "duplicate" because the protected range (f(4)-f(5))
; is in funclet catch2 but the finally's immediate parent
; is the main function, not that funclet.
-; CHECKX-NEXT: .long 10
+; CHECK-NEXT: .long 10
; ^ flags (2 => finally handler | 8 => duplicate)
-; CHECKX-NEXT: .long ([[L_before_f4]]-[[L_begin]])+1
+; CHECK-NEXT: .long ([[test1_before_f4]]-[[test1_begin]])+1
; ^ offset of start of clause
-; CHECKX-NEXT: .long ([[L_after_f5]]-[[L_begin]])+1
+; CHECK-NEXT: .long ([[test1_after_f5]]-[[test1_begin]])+1
; ^ offset of end of clause
-; CHECKX-NEXT: .long [[L_finally]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_finally]]-[[test1_begin]]
; ^ offset of start of handler
-; CHECKX-NEXT: .long [[L_end]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_end]]-[[test1_begin]]
; ^ offset of end of handler
-; CHECKX-NEXT: .long 0
+; CHECK-NEXT: .long 0
; ^ type token slot (null for finally)
; Clause 7: call f(6) is guarded by finally
; This is a "duplicate" because the protected range (f(3))
; is in funclet catch1 but the finally's immediate parent
; is the main function, not that funclet.
-; CHECKX-NEXT: .long 10
+; CHECK-NEXT: .long 10
; ^ flags (2 => finally handler | 8 => duplicate)
-; CHECKX-NEXT: .long ([[L_before_f6]]-[[L_begin]])+1
+; CHECK-NEXT: .long ([[test1_before_f6]]-[[test1_begin]])+1
; ^ offset of start of clause
-; CHECKX-NEXT: .long ([[L_after_f6]]-[[L_begin]])+1
+; CHECK-NEXT: .long ([[test1_after_f6]]-[[test1_begin]])+1
; ^ offset of end of clause
-; CHECKX-NEXT: .long [[L_finally]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_finally]]-[[test1_begin]]
; ^ offset of start of handler
-; CHECKX-NEXT: .long [[L_end]]-[[L_begin]]
+; CHECK-NEXT: .long [[test1_end]]-[[test1_begin]]
; ^ offset of end of handler
-; CHECKX-NEXT: .long 0
+; CHECK-NEXT: .long 0
; ^ type token slot (null for finally)
+
+; Test with a cleanup that has no cleanupret, and thus needs its unwind dest
+; inferred from an inner catchswitch
+;
+; corresponds to C# along the lines of:
+; void test2() {
+; try {
+; try {
+; f(1);
+; } fault {
+; try {
+; f(2);
+; } catch(type1) {
+; }
+; __unreachable();
+; }
+; } catch(type2) {
+; }
+; }
+;
+; CHECK-LABEL: test2: # @test2
+; CHECK-NEXT: [[test2_begin:.*func_begin.*]]:
+define void @test2() personality i8* bitcast (void ()* @ProcessCLRException to i8*) {
+entry:
+; CHECK: .seh_endprologue
+; CHECK: [[test2_before_f1:.+]]:
+; CHECK-NEXT: movl $1, %ecx
+; CHECK-NEXT: callq f
+; CHECK-NEXT: [[test2_after_f1:.+]]:
+ invoke void @f(i32 1)
+ to label %exit unwind label %fault
+fault:
+; CHECK: .seh_proc [[test2_fault:[^ ]+]]
+ %fault.pad = cleanuppad within none [i32 undef]
+; CHECK: .seh_endprologue
+; CHECK: [[test2_before_f2:.+]]:
+; CHECK-NEXT: movl $2, %ecx
+; CHECK-NEXT: callq f
+; CHECK-NEXT: [[test2_after_f2:.+]]:
+ invoke void @f(i32 2) ["funclet"(token %fault.pad)]
+ to label %unreachable unwind label %exn.dispatch.inner
+exn.dispatch.inner:
+ %catchswitch.inner = catchswitch within %fault.pad [label %catch1] unwind label %exn.dispatch.outer
+catch1:
+ %catch.pad1 = catchpad within %catchswitch.inner [i32 1]
+; CHECK: .seh_proc [[test2_catch1:[^ ]+]]
+ catchret from %catch.pad1 to label %unreachable
+exn.dispatch.outer:
+ %catchswitch.outer = catchswitch within none [label %catch2] unwind to caller
+catch2:
+ %catch.pad2 = catchpad within %catchswitch.outer [i32 2]
+; CHECK: .seh_proc [[test2_catch2:[^ ]+]]
+ catchret from %catch.pad2 to label %exit
+exit:
+ ret void
+unreachable:
+ unreachable
+; CHECK: [[test2_end:.*func_end.*]]:
+}
+
+; Now check for EH table in xdata (following standard xdata)
+; CHECK-LABEL: .section .xdata
+; standard xdata comes here
+; CHECK: .long 3{{$}}
+; ^ number of funclets
+; CHECK-NEXT: .long [[test2_fault]]-[[test2_begin]]
+; ^ offset from L_begin to start of 1st funclet
+; CHECK-NEXT: .long [[test2_catch1]]-[[test2_begin]]
+; ^ offset from L_begin to start of 2nd funclet
+; CHECK-NEXT: .long [[test2_catch2]]-[[test2_begin]]
+; ^ offset from L_begin to start of 3rd funclet
+; CHECK-NEXT: .long [[test2_end]]-[[test2_begin]]
+; ^ offset from L_begin to end of last funclet
+; CHECK-NEXT: .long 4
+; ^ number of EH clauses
+; Clause 1: call f(1) is guarded by fault
+; CHECK-NEXT: .long 4
+; ^ flags (4 => fault handler)
+; CHECK-NEXT: .long ([[test2_before_f1]]-[[test2_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[test2_after_f1]]-[[test2_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[test2_fault]]-[[test2_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[test2_catch1]]-[[test2_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 0
+; ^ type token slot (null for fault)
+; Clause 2: call f(1) is also guarded by catch2
+; CHECK-NEXT: .long 0
+; ^ flags (0 => catch handler)
+; CHECK-NEXT: .long ([[test2_before_f1]]-[[test2_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[test2_after_f1]]-[[test2_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[test2_catch2]]-[[test2_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[test2_end]]-[[test2_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 2
+; ^ type token of catch (from catchpad)
+; Clause 3: calls f(2) is guarded by catch1
+; CHECK-NEXT: .long 0
+; ^ flags (0 => catch handler)
+; CHECK-NEXT: .long ([[test2_before_f2]]-[[test2_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[test2_after_f2]]-[[test2_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[test2_catch1]]-[[test2_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[test2_catch2]]-[[test2_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 1
+; ^ type token of catch (from catchpad)
+; Clause 4: call f(2) is also guarded by catch2
+; This is a "duplicate" because the protected range (f(2))
+; is in funclet fault but catch2's immediate parent
+; is the main function, not that funclet.
+; CHECK-NEXT: .long 8
+; ^ flags (0 => catch handler | 8 => duplicate)
+; CHECK-NEXT: .long ([[test2_before_f2]]-[[test2_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[test2_after_f2]]-[[test2_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[test2_catch2]]-[[test2_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[test2_end]]-[[test2_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 2
+; ^ type token of catch (from catchpad)
+
+; Test with several cleanups that need to infer their unwind dests from each
+; other, the inner one needing to make the inference from an invoke, ignoring
+; not-really-unwinding calls/unwind-to-caller catchswitches, as well as some
+; internal invokes/catchswitches
+;
+; Corresponds to something like:
+; void test3() {
+; try {
+; f(1);
+; } fault { // fault1
+; try {
+; try {
+; f(2);
+; __unreachable();
+; } fault { // fault2
+; try {
+; f(3);
+; } fault { // fault3
+; try {
+; f(4);
+; } fault { // fault4
+; f(5); // no unwind edge (e.g. front-end knew it wouldn't throw but
+; didn't bother to specify nounwind)
+; try {
+; try {
+; f(6);
+; } catch(type 1) {
+; goto __unreachable;
+; }
+; } catch (type 2) { // marked "unwinds to caller" because we allow
+; // that if the unwind won't be taken (see
+; // SimplifyUnreachable & RemoveUnwindEdge)
+; goto _unreachable;
+; }
+; f(7);
+; __unreachable();
+; }
+; }
+; }
+; } fault { // fault 5
+; }
+; }
+; }
+;
+; CHECK-LABEL: test3: # @test3
+; CHECK-NEXT: [[test3_begin:.*func_begin.*]]:
+define void @test3() personality i8* bitcast (void ()* @ProcessCLRException to i8*) {
+entry:
+; CHECK: .seh_endprologue
+; CHECK: [[test3_before_f1:.+]]:
+; CHECK-NEXT: movl $1, %ecx
+; CHECK-NEXT: callq f
+; CHECK-NEXT: [[test3_after_f1:.+]]:
+ invoke void @f(i32 1)
+ to label %exit unwind label %fault1
+fault1:
+ ; check lines below since this gets reordered to end-of-func
+ %fault.pad1 = cleanuppad within none [i32 undef]
+ invoke void @f(i32 2) ["funclet"(token %fault.pad1)]
+ to label %unreachable unwind label %fault2
+fault2:
+ ; check lines below since this gets reordered to end-of-func
+ %fault.pad2 = cleanuppad within %fault.pad1 [i32 undef]
+ invoke void @f(i32 3) ["funclet"(token %fault.pad2)]
+ to label %unreachable unwind label %fault3
+fault3:
+ ; check lines below since this gets reordered to end-of-func
+ %fault.pad3 = cleanuppad within %fault.pad2 [i32 undef]
+ invoke void @f(i32 4) ["funclet"(token %fault.pad3)]
+ to label %unreachable unwind label %fault4
+fault4:
+; CHECK: .seh_proc [[test3_fault4:[^ ]+]]
+ %fault.pad4 = cleanuppad within %fault.pad3 [i32 undef]
+; CHECK: .seh_endprologue
+ call void @f(i32 5) ["funclet"(token %fault.pad4)]
+; CHECK: [[test3_before_f6:.+]]:
+; CHECK-NEXT: movl $6, %ecx
+; CHECK-NEXT: callq f
+; CHECK-NEXT: [[test3_after_f6:.+]]:
+ invoke void @f(i32 6) ["funclet"(token %fault.pad4)]
+ to label %fault4.cont unwind label %exn.dispatch1
+fault4.cont:
+; CHECK: # %fault4.cont
+; CHECK: [[test3_before_f7:.+]]:
+; CHECK-NEXT: movl $7, %ecx
+; CHECK-NEXT: callq f
+; CHECK-NEXT: [[test3_after_f7:.+]]:
+ invoke void @f(i32 7) ["funclet"(token %fault.pad4)]
+ to label %unreachable unwind label %fault5
+exn.dispatch1:
+ %catchswitch1 = catchswitch within %fault.pad4 [label %catch1] unwind label %exn.dispatch2
+catch1:
+ %catch.pad1 = catchpad within %catchswitch1 [i32 1]
+; CHECK: .seh_proc [[test3_catch1:[^ ]+]]
+ catchret from %catch.pad1 to label %unreachable
+exn.dispatch2:
+ %catchswitch2 = catchswitch within %fault.pad4 [label %catch2] unwind to caller
+catch2:
+ %catch.pad2 = catchpad within %catchswitch2 [i32 2]
+; CHECK: .seh_proc [[test3_catch2:[^ ]+]]
+ catchret from %catch.pad2 to label %unreachable
+fault5:
+; CHECK: .seh_proc [[test3_fault5:[^ ]+]]
+ %fault.pad5 = cleanuppad within %fault.pad1 [i32 undef]
+; CHECK: .seh_endprologue
+cleanupret from %fault.pad5 unwind to caller
+exit:
+ ret void
+unreachable:
+ unreachable
+; CHECK: .seh_proc [[test3_fault3:[^ ]+]]
+; CHECK: # %fault3
+; CHECK: .seh_endprologue
+; CHECK: [[test3_before_f4:.+]]:
+; CHECK-NEXT: movl $4, %ecx
+; CHECK-NEXT: callq f
+; CHECK-NEXT: [[test3_after_f4:.+]]:
+; CHECK: .seh_proc [[test3_fault2:[^ ]+]]
+; CHECK: # %fault2
+; CHECK: .seh_endprologue
+; CHECK: [[test3_before_f3:.+]]:
+; CHECK-NEXT: movl $3, %ecx
+; CHECK-NEXT: callq f
+; CHECK-NEXT: [[test3_after_f3:.+]]:
+; CHECK: .seh_proc [[test3_fault1:[^ ]+]]
+; CHECK: # %fault1
+; CHECK: .seh_endprologue
+; CHECK: [[test3_before_f2:.+]]:
+; CHECK-NEXT: movl $2, %ecx
+; CHECK-NEXT: callq f
+; CHECK-NEXT: [[test3_after_f2:.+]]:
+; CHECK: [[test3_end:.*func_end.*]]:
+}
+
+; Now check for EH table in xdata (following standard xdata)
+; CHECK-LABEL: .section .xdata
+; standard xdata comes here
+; CHECK: .long 7{{$}}
+; ^ number of funclets
+; CHECK-NEXT: .long [[test3_fault4]]-[[test3_begin]]
+; ^ offset from L_begin to start of 1st funclet
+; CHECK-NEXT: .long [[test3_catch1]]-[[test3_begin]]
+; ^ offset from L_begin to start of 2nd funclet
+; CHECK-NEXT: .long [[test3_catch2]]-[[test3_begin]]
+; ^ offset from L_begin to start of 3rd funclet
+; CHECK-NEXT: .long [[test3_fault5]]-[[test3_begin]]
+; ^ offset from L_begin to start of 4th funclet
+; CHECK-NEXT: .long [[test3_fault3]]-[[test3_begin]]
+; ^ offset from L_begin to start of 5th funclet
+; CHECK-NEXT: .long [[test3_fault2]]-[[test3_begin]]
+; ^ offset from L_begin to start of 6th funclet
+; CHECK-NEXT: .long [[test3_fault1]]-[[test3_begin]]
+; ^ offset from L_begin to start of 7th funclet
+; CHECK-NEXT: .long [[test3_end]]-[[test3_begin]]
+; ^ offset from L_begin to end of last funclet
+; CHECK-NEXT: .long 10
+; ^ number of EH clauses
+; Clause 1: call f(1) is guarded by fault1
+; CHECK-NEXT: .long 4
+; ^ flags (4 => fault handler)
+; CHECK-NEXT: .long ([[test3_before_f1]]-[[test3_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[test3_after_f1]]-[[test3_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[test3_fault1]]-[[test3_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[test3_end]]-[[test3_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 0
+; ^ type token slot (null for fault)
+; Clause 3: call f(6) is guarded by catch1
+; CHECK-NEXT: .long 0
+; ^ flags (0 => catch handler)
+; CHECK-NEXT: .long ([[test3_before_f6]]-[[test3_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[test3_after_f6]]-[[test3_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[test3_catch1]]-[[test3_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[test3_catch2]]-[[test3_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 1
+; ^ type token of catch (from catchpad)
+; Clause 3: call f(6) is also guarded by catch2
+; CHECK-NEXT: .long 0
+; ^ flags (0 => catch handler)
+; CHECK-NEXT: .long ([[test3_before_f6]]-[[test3_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[test3_after_f6]]-[[test3_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[test3_catch2]]-[[test3_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[test3_fault5]]-[[test3_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 2
+; ^ type token of catch (from catchpad)
+; Clause 4: call f(7) is guarded by fault5
+; This is a "duplicate" because the protected range (f(6)-f(7))
+; is in funclet fault4 but fault5's immediate parent
+; is fault1, not that funclet.
+; CHECK-NEXT: .long 12
+; ^ flags (4 => fault handler | 8 => duplicate)
+; CHECK-NEXT: .long ([[test3_before_f7]]-[[test3_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[test3_after_f7]]-[[test3_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[test3_fault5]]-[[test3_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[test3_fault3]]-[[test3_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 0
+; ^ type token slot (null for fault)
+; Clause 5: call f(4) is guarded by fault4
+; CHECK-NEXT: .long 4
+; ^ flags (4 => fault handler)
+; CHECK-NEXT: .long ([[test3_before_f4]]-[[test3_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[test3_after_f4]]-[[test3_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[test3_fault4]]-[[test3_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[test3_catch1]]-[[test3_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 0
+; ^ type token slot (null for fault)
+; Clause 6: call f(4) is also guarded by fault5
+; This is a "duplicate" because the protected range (f(4))
+; is in funclet fault3 but fault5's immediate parent
+; is fault1, not that funclet.
+; CHECK-NEXT: .long 12
+; ^ flags (4 => fault handler)
+; CHECK-NEXT: .long ([[test3_before_f4]]-[[test3_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[test3_after_f4]]-[[test3_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[test3_fault5]]-[[test3_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[test3_fault3]]-[[test3_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 0
+; ^ type token slot (null for fault)
+; Clause 7: call f(3) is guarded by fault3
+; CHECK-NEXT: .long 4
+; ^ flags (4 => fault handler)
+; CHECK-NEXT: .long ([[test3_before_f3]]-[[test3_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[test3_after_f3]]-[[test3_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[test3_fault3]]-[[test3_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[test3_fault2]]-[[test3_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 0
+; ^ type token slot (null for fault)
+; Clause 8: call f(3) is guarded by fault5
+; This is a "duplicate" because the protected range (f(3))
+; is in funclet fault2 but fault5's immediate parent
+; is fault1, not that funclet.
+; CHECK-NEXT: .long 12
+; ^ flags (4 => fault handler | 8 => duplicate)
+; CHECK-NEXT: .long ([[test3_before_f3]]-[[test3_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[test3_after_f3]]-[[test3_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[test3_fault5]]-[[test3_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[test3_fault3]]-[[test3_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 0
+; ^ type token slot (null for fault)
+; Clause 9: call f(2) is guarded by fault2
+; CHECK-NEXT: .long 4
+; ^ flags (4 => fault handler)
+; CHECK-NEXT: .long ([[test3_before_f2]]-[[test3_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[test3_after_f2]]-[[test3_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[test3_fault2]]-[[test3_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[test3_fault1]]-[[test3_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 0
+; ^ type token slot (null for fault)
+; Clause 10: call f(2) is guarded by fault5
+; CHECK-NEXT: .long 4
+; ^ flags (4 => fault handler)
+; CHECK-NEXT: .long ([[test3_before_f2]]-[[test3_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[test3_after_f2]]-[[test3_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[test3_fault5]]-[[test3_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[test3_fault3]]-[[test3_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 0
+; ^ type token slot (null for fault)