[llvm] r256760 - [WinEH] Update CoreCLR EH state numbering

Joseph Tremoulet via llvm-commits llvm-commits at lists.llvm.org
Mon Jan 4 08:16:01 PST 2016


Author: josepht
Date: Mon Jan  4 10:16:01 2016
New Revision: 256760

URL: http://llvm.org/viewvc/llvm-project?rev=256760&view=rev
Log:
[WinEH] Update CoreCLR EH state numbering

Summary:
Fix the CLR state numbering to generate correct tables, and update the lit
test to verify them.

The CLR 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.

Table generation requires each state's "unwind dest" state to have a lower
state number than the given state.

Since HandlerParentState can be computed as a function of a pad's
ParentPad, and TryParentState can be computed as a function of its unwind
dest and the TryParentStates of its children, the CLR state numbering
algorithm first computes HandlerParentState in a top-down pass, then
computes TryParentState in a bottom-up pass.

Also reword some comments/names in the CLR EH table generation to make the
distinction between the different kinds of "parent" clear.


Reviewers: rnk, andrew.w.kaylor, majnemer

Subscribers: AndyAyers, llvm-commits

Differential Revision: http://reviews.llvm.org/D15325

Modified:
    llvm/trunk/include/llvm/CodeGen/WinEHFuncInfo.h
    llvm/trunk/lib/CodeGen/AsmPrinter/WinException.cpp
    llvm/trunk/lib/CodeGen/WinEHPrepare.cpp
    llvm/trunk/test/CodeGen/X86/wineh-coreclr.ll

Modified: llvm/trunk/include/llvm/CodeGen/WinEHFuncInfo.h
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/CodeGen/WinEHFuncInfo.h?rev=256760&r1=256759&r2=256760&view=diff
==============================================================================
--- llvm/trunk/include/llvm/CodeGen/WinEHFuncInfo.h (original)
+++ llvm/trunk/include/llvm/CodeGen/WinEHFuncInfo.h Mon Jan  4 10:16:01 2016
@@ -83,7 +83,9 @@ enum class ClrHandlerType { Catch, Final
 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;
 };
 

Modified: llvm/trunk/lib/CodeGen/AsmPrinter/WinException.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/CodeGen/AsmPrinter/WinException.cpp?rev=256760&r1=256759&r2=256760&view=diff
==============================================================================
--- llvm/trunk/lib/CodeGen/AsmPrinter/WinException.cpp (original)
+++ llvm/trunk/lib/CodeGen/AsmPrinter/WinException.cpp Mon Jan  4 10:16:01 2016
@@ -976,32 +976,32 @@ void WinException::emitExceptHandlerTabl
   }
 }
 
-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 @@ void WinException::emitCLRExceptionTable
         FuncInfo.ClrEHUnwindMap[State].Handler.get<MachineBasicBlock *>();
     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 @@ void WinException::emitCLRExceptionTable
   SmallVector<int, 4> 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 @@ void WinException::emitCLRExceptionTable
     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 @@ void WinException::emitCLRExceptionTable
         // 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;

Modified: llvm/trunk/lib/CodeGen/WinEHPrepare.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/CodeGen/WinEHPrepare.cpp?rev=256760&r1=256759&r2=256760&view=diff
==============================================================================
--- llvm/trunk/lib/CodeGen/WinEHPrepare.cpp (original)
+++ llvm/trunk/lib/CodeGen/WinEHPrepare.cpp Mon Jan  4 10:16:01 2016
@@ -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/MachineBasicBlock.h"
@@ -436,11 +438,12 @@ void llvm::calculateWinCXXEHStateNumbers
   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;
@@ -454,82 +457,199 @@ void llvm::calculateClrEHStateNumbers(co
   if (!FuncInfo.EHPadStateMap.empty())
     return;
 
-  SmallVector<std::pair<const Instruction *, int>, 8> Worklist;
+  // 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.
 
-  // 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.
+  // Seed a worklist with pads that have no parent.
+  SmallVector<std::pair<const Instruction *, int>, 8> Worklist;
   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();
+    int HandlerParentState;
+    std::tie(Pad, HandlerParentState) = 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();
+    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);
 }
 

Modified: llvm/trunk/test/CodeGen/X86/wineh-coreclr.ll
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/CodeGen/X86/wineh-coreclr.ll?rev=256760&r1=256759&r2=256760&view=diff
==============================================================================
--- llvm/trunk/test/CodeGen/X86/wineh-coreclr.ll (original)
+++ llvm/trunk/test/CodeGen/X86/wineh-coreclr.ll Mon Jan  4 10:16:01 2016
@@ -26,34 +26,34 @@ declare i8 addrspace(1)* @llvm.eh.except
 ;   }
 ;   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 @@ catch1.body:
 ; 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 @@ catch2.body:
 ; 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 @@ fault.pad:
 ; 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 @@ finally.pad:
 ; 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)




More information about the llvm-commits mailing list