Index: include/llvm/CodeGen/WinEHFuncInfo.h =================================================================== --- include/llvm/CodeGen/WinEHFuncInfo.h +++ include/llvm/CodeGen/WinEHFuncInfo.h @@ -83,7 +83,9 @@ struct ClrEHUnwindMapEntry { MBBOrBasicBlock Handler; uint32_t TypeToken; - int Parent; + int HandlerParentState; ///< Outer handler enclosing this entry's handler + int TryParentState; ///< Outer try region enclosing this entry's try region, + ///< treating later catches on same try as "outer" ClrHandlerType HandlerType; }; Index: lib/CodeGen/AsmPrinter/WinException.cpp =================================================================== --- lib/CodeGen/AsmPrinter/WinException.cpp +++ lib/CodeGen/AsmPrinter/WinException.cpp @@ -976,32 +976,32 @@ } } -static int getRank(const WinEHFuncInfo &FuncInfo, int State) { +static int getTryRank(const WinEHFuncInfo &FuncInfo, int State) { int Rank = 0; while (State != -1) { ++Rank; - State = FuncInfo.ClrEHUnwindMap[State].Parent; + State = FuncInfo.ClrEHUnwindMap[State].TryParentState; } return Rank; } -static int getAncestor(const WinEHFuncInfo &FuncInfo, int Left, int Right) { - int LeftRank = getRank(FuncInfo, Left); - int RightRank = getRank(FuncInfo, Right); +static int getTryAncestor(const WinEHFuncInfo &FuncInfo, int Left, int Right) { + int LeftRank = getTryRank(FuncInfo, Left); + int RightRank = getTryRank(FuncInfo, Right); while (LeftRank < RightRank) { - Right = FuncInfo.ClrEHUnwindMap[Right].Parent; + Right = FuncInfo.ClrEHUnwindMap[Right].TryParentState; --RightRank; } while (RightRank < LeftRank) { - Left = FuncInfo.ClrEHUnwindMap[Left].Parent; + Left = FuncInfo.ClrEHUnwindMap[Left].TryParentState; --LeftRank; } while (Left != Right) { - Left = FuncInfo.ClrEHUnwindMap[Left].Parent; - Right = FuncInfo.ClrEHUnwindMap[Right].Parent; + Left = FuncInfo.ClrEHUnwindMap[Left].TryParentState; + Right = FuncInfo.ClrEHUnwindMap[Right].TryParentState; } return Left; @@ -1035,9 +1035,9 @@ FuncInfo.ClrEHUnwindMap[State].Handler.get(); HandlerStates[HandlerBlock] = State; // Use this loop through all handlers to verify our assumption (used in - // the MinEnclosingState computation) that ancestors have lower state - // numbers than their descendants. - assert(FuncInfo.ClrEHUnwindMap[State].Parent < State && + // the MinEnclosingState computation) that enclosing funclets have lower + // state numbers than their enclosed funclets. + assert(FuncInfo.ClrEHUnwindMap[State].HandlerParentState < State && "ill-formed state numbering"); } // Map the main function to the NullState. @@ -1070,7 +1070,6 @@ SmallVector MinClauseMap((size_t)NumStates, NumStates); // Visit the root function and each funclet. - for (MachineFunction::const_iterator FuncletStart = MF->begin(), FuncletEnd = MF->begin(), End = MF->end(); @@ -1100,17 +1099,18 @@ for (const auto &StateChange : InvokeStateChangeIterator::range(FuncInfo, FuncletStart, FuncletEnd)) { // Close any try regions we're not still under - int AncestorState = - getAncestor(FuncInfo, CurrentState, StateChange.NewState); - while (CurrentState != AncestorState) { - assert(CurrentState != NullState && "Failed to find ancestor!"); + int StillPendingState = + getTryAncestor(FuncInfo, CurrentState, StateChange.NewState); + while (CurrentState != StillPendingState) { + assert(CurrentState != NullState && + "Failed to find still-pending state!"); // Close the pending clause Clauses.push_back({CurrentStartLabel, StateChange.PreviousEndLabel, CurrentState, FuncletState}); - // Now the parent handler is current - CurrentState = FuncInfo.ClrEHUnwindMap[CurrentState].Parent; + // Now the next-outer try region is current + CurrentState = FuncInfo.ClrEHUnwindMap[CurrentState].TryParentState; // Pop the new start label from the handler stack if we've exited all - // descendants of the corresponding handler. + // inner try regions of the corresponding try region. if (HandlerStack.back().second == CurrentState) CurrentStartLabel = HandlerStack.pop_back_val().first; } @@ -1121,7 +1121,8 @@ // it. for (int EnteredState = StateChange.NewState; EnteredState != CurrentState; - EnteredState = FuncInfo.ClrEHUnwindMap[EnteredState].Parent) { + EnteredState = + FuncInfo.ClrEHUnwindMap[EnteredState].TryParentState) { int &MinEnclosingState = MinClauseMap[EnteredState]; if (FuncletState < MinEnclosingState) MinEnclosingState = FuncletState; Index: lib/CodeGen/WinEHPrepare.cpp =================================================================== --- lib/CodeGen/WinEHPrepare.cpp +++ lib/CodeGen/WinEHPrepare.cpp @@ -17,7 +17,9 @@ //===----------------------------------------------------------------------===// #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/WinEHFuncInfo.h" @@ -433,11 +435,12 @@ 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; @@ -451,82 +454,199 @@ 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, 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(FirstNonPHI)) + ParentPad = CPI->getParentPad(); + else if (const auto *CSI = dyn_cast(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(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(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(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(Pad)) { - SmallVector Handlers; - for (const BasicBlock *CatchPadBB : CatchSwitch->handlers()) { - const auto *Catch = cast(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(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(Pad); + int CatchState = -1, FollowerState = -1; + SmallVector 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(CatchBlock->getFirstNonPHI()); uint32_t TypeToken = static_cast( cast(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(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(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()->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(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(Pad); + UnwindDest = nullptr; + for (const User *U : Cleanup->users()) { + if (auto *CleanupRet = dyn_cast(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(U)) { + UserUnwindDest = Invoke->getUnwindDest(); + } else if (auto *CatchSwitch = dyn_cast(U)) { + UserUnwindDest = CatchSwitch->getUnwindDest(); + } else if (auto *ChildCleanup = dyn_cast(U)) { + int UserState = FuncInfo.EHPadStateMap[ChildCleanup]; + int UserUnwindState = + FuncInfo.ClrEHUnwindMap[UserState].TryParentState; + if (UserUnwindState != -1) + UserUnwindDest = FuncInfo.ClrEHUnwindMap[UserUnwindState] + .Handler.get(); } + + // 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(UserUnwindPad)) + UserUnwindParent = CSI->getParentPad(); + else + UserUnwindParent = + cast(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); } Index: test/CodeGen/X86/wineh-coreclr.ll =================================================================== --- test/CodeGen/X86/wineh-coreclr.ll +++ test/CodeGen/X86/wineh-coreclr.ll @@ -26,34 +26,34 @@ ; } ; 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 @@ -64,19 +64,19 @@ ; 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 @@ -87,25 +87,25 @@ ; 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 @@ -113,22 +113,22 @@ ; 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 @@ -138,130 +138,555 @@ ; 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)