Index: lib/CodeGen/WinEHPrepare.cpp =================================================================== --- lib/CodeGen/WinEHPrepare.cpp +++ lib/CodeGen/WinEHPrepare.cpp @@ -202,20 +202,22 @@ } // Given BB which ends in an unwind edge, return the EHPad that this BB belongs -// to. If the unwind edge came from an invoke, return null. +// to. If the unwind edge came from an invoke, return null. If ParentPad is +// non-null, EHPads that do not have it as their ParentPad will also be ignored +// (returning null). static const BasicBlock *getEHPadFromPredecessor(const BasicBlock *BB, Value *ParentPad) { const TerminatorInst *TI = BB->getTerminator(); if (isa(TI)) return nullptr; if (auto *CatchSwitch = dyn_cast(TI)) { - if (CatchSwitch->getParentPad() != ParentPad) + if (ParentPad && CatchSwitch->getParentPad() != ParentPad) return nullptr; return BB; } assert(!TI->isEHPad() && "unexpected EHPad!"); auto *CleanupPad = cast(TI)->getCleanupPad(); - if (CleanupPad->getParentPad() != ParentPad) + if (ParentPad && CleanupPad->getParentPad() != ParentPad) return nullptr; return CleanupPad->getParent(); } @@ -438,6 +440,72 @@ return FuncInfo.ClrEHUnwindMap.size() - 1; } +static bool isTopLevelPadForCLR(const Instruction *EHPad) { + // For the CLR, a pad is "top-level" if it is not a catchpad and exceptions + // that propagate out of it continue straight up to the function's caller. + + // A catchswitch is directly annotated as unwinding to caller or not + if (const auto *CSI = dyn_cast(EHPad)) + return CSI->unwindsToCaller(); + + // Rather than redundantly mark catchpads and their enclosing catchswitches + // as toplevel when both unwind to caller, just rely on visiting the catchpad + // as a consequence of visiting its catchswitch. + if (isa(EHPad)) + return false; + + // The pad is a cleanuppad, which is the tricky case because what we want + // to know is if exceptions propagating out of it escape to the caller or + // not, but don't have direct linkage to that information. The information + // will be available on any cleanuprets for the cleanuppad, as well as any + // nested catchpads. Likewise for nested cleanuppads, which makes the + // problem recursive, so use a worklist, seeded with the given pad. + SmallVector Worklist; + SmallPtrSet UselessCleanups; + do { + Worklist.push_back(cast(EHPad)); + do { + const CleanupPadInst *Cleanup = Worklist.pop_back_val(); + for (const User *U : Cleanup->users()) { + if (const auto *CRI = dyn_cast(U)) + return CRI->unwindsToCaller(); + if (const auto *CSI = dyn_cast(U)) { + if (CSI->hasUnwindDest()) + return false; + // This catchswitch is annotated as "unwinds to caller", but we can't + // trust that means what it says (e.g. SimplifyUnreachable might have + // rewritten it that way because it initially unwound to an inner + // cleanuppad containing unreachable). So just ignore this user. + continue; + } + // FIXME: This should also be checking for invokes in the cleanup; their + // unwind dest must also agree. Calls in the cleanup have the same + // issue as catchswitch, that they might not necessarily imply unwinding + // to caller. + if (const auto *CPI = dyn_cast(U)) + if (UselessCleanups.count(CPI)) + Worklist.push_back(CPI); + } + } while (!Worklist.empty()); + // Couldn't find any unwind edges within the cleanup. We have to keep it + // within its parent funclet, but can assume that it is not in any try + // regions within the parent, so try to find the parent's unwind dest. + const Value *Parent = cast(EHPad)->getParentPad(); + // Check for the "none" sentinel that indicates this cleanup isn't nested + // in another funclet, which implies it is top-level. + if (isa(Parent)) + return true; + // As above, catchpads are never considered toplevel, because their + // catchswitches may be and they will be visited from their catchswitch. + if (isa(Parent)) + return false; + // Parent must be a cleanuppad; report this one as useless so we don't + // revisit it, and recurse up to the parent. + UselessCleanups.insert(cast(EHPad)); + EHPad = cast(Parent); + } while (true); +} + void llvm::calculateClrEHStateNumbers(const Function *Fn, WinEHFuncInfo &FuncInfo) { // Return if it's already been done. @@ -454,7 +522,7 @@ if (BB.isLandingPad()) report_fatal_error("CoreCLR EH cannot use landingpads"); const Instruction *FirstNonPHI = BB.getFirstNonPHI(); - if (!isTopLevelPadForMSVC(FirstNonPHI)) + if (!isTopLevelPadForCLR(FirstNonPHI)) continue; // queue this with sentinel parent state -1 to mean unwind to caller. Worklist.emplace_back(FirstNonPHI, -1); @@ -465,8 +533,7 @@ int ParentState; std::tie(Pad, ParentState) = Worklist.pop_back_val(); - Value *ParentPad; - int PredState; + int SelfState; 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)) @@ -474,49 +541,38 @@ // CoreCLR personality uses arity to distinguish faults from finallies. const BasicBlock *PadBlock = Cleanup->getParent(); ClrHandlerType HandlerType = - (Cleanup->getNumOperands() ? ClrHandlerType::Fault - : ClrHandlerType::Finally); - int NewState = + (Cleanup->getNumArgOperands() ? ClrHandlerType::Fault + : ClrHandlerType::Finally); + SelfState = 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; + // Walk the catchpads in reverse order since the early ones are reported + // like descendants of the later ones in the EH tables. int NewState = ParentState; - for (auto HandlerI = Handlers.rbegin(), HandlerE = Handlers.rend(); - HandlerI != HandlerE; ++HandlerI) { - const CatchPadInst *Catch = *HandlerI; - const BasicBlock *PadBlock = Catch->getParent(); + SmallVector CatchBlocks(CatchSwitch->handlers()); + for (auto CBI = CatchBlocks.rbegin(), CBE = CatchBlocks.rend(); + CBI != CBE; ++CBI) { + const auto *Catch = cast((*CBI)->getFirstNonPHI()); uint32_t TypeToken = static_cast( cast(Catch->getArgOperand(0))->getZExtValue()); NewState = addClrEHHandler(FuncInfo, NewState, ClrHandlerType::Catch, - TypeToken, PadBlock); + TypeToken, *CBI); FuncInfo.EHPadStateMap[Catch] = NewState; } - for (const auto *CatchPad : Handlers) { - for (const User *U : CatchPad->users()) { - const auto *UserI = cast(U); - if (UserI->isEHPad()) - Worklist.emplace_back(UserI, ParentState); - } - } - PredState = NewState; - ParentPad = CatchSwitch->getParentPad(); + // The catchswitch uses the same state number as the first catch (or, if + // we ever see an empty catchswitch, the state number of its successor). + SelfState = NewState; } else { llvm_unreachable("Unexpected EH pad"); } + // Record this pad's state. + FuncInfo.EHPadStateMap[Pad] = SelfState; + // 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); + if ((Pred = getEHPadFromPredecessor(Pred, nullptr))) + Worklist.emplace_back(Pred->getFirstNonPHI(), SelfState); } } Index: test/CodeGen/X86/wineh-coreclr.ll =================================================================== --- test/CodeGen/X86/wineh-coreclr.ll +++ test/CodeGen/X86/wineh-coreclr.ll @@ -40,7 +40,7 @@ ; CHECK-NEXT: callq f ; CHECK-NEXT: [[L_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:.+]]: @@ -48,11 +48,11 @@ ; CHECK-NEXT: callq f ; CHECK-NEXT: [[L_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] + 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 [[L_catch1:[^ ]+]] ; CHECK: .seh_stackalloc [[FuncletFrameSize:[0-9]+]] ; ^ all funclets use the same frame size @@ -64,18 +64,18 @@ ; CHECK: movq %rdx, %rcx ; ^ exception pointer passed in rdx ; CHECK-NEXT: callq g - %exn1 = call i8 addrspace(1)* @llvm.eh.exceptionpointer.p1i8(token %catch1) + %exn1 = call i8 addrspace(1)* @llvm.eh.exceptionpointer.p1i8(token %catch.pad1) call void @g(i8 addrspace(1)* %exn1) ; CHECK: [[L_before_f3:.+]]: ; CHECK-NEXT: movl $3, %ecx ; CHECK-NEXT: callq f ; CHECK-NEXT: [[L_after_f3:.+]]: invoke void @f(i32 3) - to label %catch1.ret unwind label %finally.pad + to label %catch1.ret unwind label %finally catch1.ret: - catchret from %catch1 to label %finally.clone -catch2.body: - %catch2 = catchpad within %cs1 [i32 2] + catchret from %catch.pad1 to label %finally.clone +catch2: + %catch.pad2 = catchpad within %catchswitch [i32 2] ; CHECK: .seh_proc [[L_catch2:[^ ]+]] ; CHECK: .seh_stackalloc [[FuncletFrameSize:[0-9]+]] ; ^ all funclets use the same frame size @@ -87,14 +87,14 @@ ; CHECK: movq %rdx, %rcx ; ^ exception pointer passed in rdx ; CHECK-NEXT: callq g - %exn2 = call i8 addrspace(1)* @llvm.eh.exceptionpointer.p1i8(token %catch2) + %exn2 = call i8 addrspace(1)* @llvm.eh.exceptionpointer.p1i8(token %catch.pad2) call void @g(i8 addrspace(1)* %exn2) ; CHECK: [[L_before_f4:.+]]: ; CHECK-NEXT: movl $4, %ecx ; CHECK-NEXT: callq f ; CHECK-NEXT: [[L_after_f4:.+]]: invoke void @f(i32 4) - to label %try_in_catch unwind label %finally.pad + to label %try_in_catch unwind label %finally try_in_catch: ; CHECK: # %try_in_catch ; CHECK: [[L_before_f5:.+]]: @@ -102,10 +102,10 @@ ; CHECK-NEXT: callq f ; CHECK-NEXT: [[L_after_f5:.+]]: invoke void @f(i32 5) - to label %catch2.ret unwind label %fault.pad -fault.pad: + to label %catch2.ret unwind label %fault +fault: ; CHECK: .seh_proc [[L_fault:[^ ]+]] - %fault = cleanuppad within none [i32 undef] + %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 @@ -118,17 +118,17 @@ ; CHECK-NEXT: callq f ; CHECK-NEXT: [[L_after_f6:.+]]: invoke void @f(i32 6) - to label %fault.ret unwind label %finally.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: +finally: ; CHECK: .seh_proc [[L_finally:[^ ]+]] - %finally = cleanuppad within none [] + %finally.pad = cleanuppad within none [] ; CHECK: .seh_stackalloc [[FuncletFrameSize:[0-9]+]] ; ^ all funclets use the same frame size ; CHECK: movq [[PSPSymOffset]](%rcx), %rcx @@ -139,129 +139,127 @@ ; CHECK-NEXT: movl $7, %ecx ; CHECK-NEXT: callq f call void @f(i32 7) - cleanupret from %finally unwind to caller + cleanupret from %finally.pad unwind to caller tail: call void @f(i32 8) ret void ; CHECK: [[L_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 [[L_catch1]]-[[L_begin]] ; ^ offset from L_begin to start of 1st funclet -; CHECKX-NEXT: .long [[L_catch2]]-[[L_begin]] +; CHECK-NEXT: .long [[L_catch2]]-[[L_begin]] ; ^ offset from L_begin to start of 2nd funclet -; CHECKX-NEXT: .long [[L_fault]]-[[L_begin]] +; CHECK-NEXT: .long [[L_fault]]-[[L_begin]] ; ^ offset from L_begin to start of 3rd funclet -; CHECKX-NEXT: .long [[L_finally]]-[[L_begin]] +; CHECK-NEXT: .long [[L_finally]]-[[L_begin]] ; ^ offset from L_begin to start of 4th funclet -; CHECKX-NEXT: .long [[L_end]]-[[L_begin]] +; CHECK-NEXT: .long [[L_end]]-[[L_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 ([[L_before_f2]]-[[L_begin]])+1 ; ^ offset of start of clause -; CHECKX-NEXT: .long ([[L_after_f2]]-[[L_begin]])+1 +; CHECK-NEXT: .long ([[L_after_f2]]-[[L_begin]])+1 ; ^ offset of end of clause -; CHECKX-NEXT: .long [[L_catch1]]-[[L_begin]] +; CHECK-NEXT: .long [[L_catch1]]-[[L_begin]] ; ^ offset of start of handler -; CHECKX-NEXT: .long [[L_catch2]]-[[L_begin]] +; CHECK-NEXT: .long [[L_catch2]]-[[L_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 ([[L_before_f2]]-[[L_begin]])+1 ; ^ offset of start of clause -; CHECKX-NEXT: .long ([[L_after_f2]]-[[L_begin]])+1 +; CHECK-NEXT: .long ([[L_after_f2]]-[[L_begin]])+1 ; ^ offset of end of clause -; CHECKX-NEXT: .long [[L_catch2]]-[[L_begin]] +; CHECK-NEXT: .long [[L_catch2]]-[[L_begin]] ; ^ offset of start of handler -; CHECKX-NEXT: .long [[L_fault]]-[[L_begin]] +; CHECK-NEXT: .long [[L_fault]]-[[L_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 ([[L_before_f1]]-[[L_begin]])+1 ; ^ offset of start of clause -; CHECKX-NEXT: .long ([[L_after_f2]]-[[L_begin]])+1 +; CHECK-NEXT: .long ([[L_after_f2]]-[[L_begin]])+1 ; ^ offset of end of clause -; CHECKX-NEXT: .long [[L_finally]]-[[L_begin]] +; CHECK-NEXT: .long [[L_finally]]-[[L_begin]] ; ^ offset of start of handler -; CHECKX-NEXT: .long [[L_end]]-[[L_begin]] +; CHECK-NEXT: .long [[L_end]]-[[L_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 ([[L_before_f3]]-[[L_begin]])+1 ; ^ offset of start of clause -; CHECKX-NEXT: .long ([[L_after_f3]]-[[L_begin]])+1 +; CHECK-NEXT: .long ([[L_after_f3]]-[[L_begin]])+1 ; ^ offset of end of clause -; CHECKX-NEXT: .long [[L_finally]]-[[L_begin]] +; CHECK-NEXT: .long [[L_finally]]-[[L_begin]] ; ^ offset of start of handler -; CHECKX-NEXT: .long [[L_end]]-[[L_begin]] +; CHECK-NEXT: .long [[L_end]]-[[L_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 ([[L_before_f5]]-[[L_begin]])+1 ; ^ offset of start of clause -; CHECKX-NEXT: .long ([[L_after_f5]]-[[L_begin]])+1 +; CHECK-NEXT: .long ([[L_after_f5]]-[[L_begin]])+1 ; ^ offset of end of clause -; CHECKX-NEXT: .long [[L_fault]]-[[L_begin]] +; CHECK-NEXT: .long [[L_fault]]-[[L_begin]] ; ^ offset of start of handler -; CHECKX-NEXT: .long [[L_finally]]-[[L_begin]] +; CHECK-NEXT: .long [[L_finally]]-[[L_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 ([[L_before_f4]]-[[L_begin]])+1 ; ^ offset of start of clause -; CHECKX-NEXT: .long ([[L_after_f5]]-[[L_begin]])+1 +; CHECK-NEXT: .long ([[L_after_f5]]-[[L_begin]])+1 ; ^ offset of end of clause -; CHECKX-NEXT: .long [[L_finally]]-[[L_begin]] +; CHECK-NEXT: .long [[L_finally]]-[[L_begin]] ; ^ offset of start of handler -; CHECKX-NEXT: .long [[L_end]]-[[L_begin]] +; CHECK-NEXT: .long [[L_end]]-[[L_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 ([[L_before_f6]]-[[L_begin]])+1 ; ^ offset of start of clause -; CHECKX-NEXT: .long ([[L_after_f6]]-[[L_begin]])+1 +; CHECK-NEXT: .long ([[L_after_f6]]-[[L_begin]])+1 ; ^ offset of end of clause -; CHECKX-NEXT: .long [[L_finally]]-[[L_begin]] +; CHECK-NEXT: .long [[L_finally]]-[[L_begin]] ; ^ offset of start of handler -; CHECKX-NEXT: .long [[L_end]]-[[L_begin]] +; CHECK-NEXT: .long [[L_end]]-[[L_begin]] ; ^ offset of end of handler -; CHECKX-NEXT: .long 0 +; CHECK-NEXT: .long 0 ; ^ type token slot (null for finally)