diff --git a/llvm/include/llvm/Analysis/EHPersonalities.h b/llvm/include/llvm/Analysis/EHPersonalities.h --- a/llvm/include/llvm/Analysis/EHPersonalities.h +++ b/llvm/include/llvm/Analysis/EHPersonalities.h @@ -91,12 +91,13 @@ } /// Return true if this personality may be safely removed if there -/// are no invoke instructions remaining in the current function. +/// are no invoke/unwindabort instructions remaining in the current function. inline bool isNoOpWithoutInvoke(EHPersonality Pers) { switch (Pers) { case EHPersonality::Unknown: return false; - // All known personalities currently have this behavior + // All known personalities currently have no effect without an 'invoke' or + // 'call unwindabort' in the function. default: return true; } diff --git a/llvm/include/llvm/Transforms/Utils/Local.h b/llvm/include/llvm/Transforms/Utils/Local.h --- a/llvm/include/llvm/Transforms/Utils/Local.h +++ b/llvm/include/llvm/Transforms/Utils/Local.h @@ -354,6 +354,12 @@ BasicBlock *UnwindEdge, DomTreeUpdater *DTU = nullptr); +enum class RemoveUnwindEdgeMode { + Normal, + Nounwind, + Unwindabort, +}; + /// Replace 'BB's terminator with one that does not have an unwind successor /// block. Rewrites `invoke` to `call`, etc. Updates any PHIs in unwind /// successor. Returns the instruction that replaced the original terminator, @@ -361,7 +367,11 @@ /// /// \param BB Block whose terminator will be replaced. Its terminator must /// have an unwind successor. -Instruction *removeUnwindEdge(BasicBlock *BB, DomTreeUpdater *DTU = nullptr); +/// \param Mode Is the edge known to be 'nounwind' or 'unwindabort'? (set if +/// e.g. the unwind edge is known to be unreachable/reaches a +/// 'resume unwindabort') +void removeUnwindEdge(BasicBlock *BB, RemoveUnwindEdgeMode Mode, + DomTreeUpdater *DTU = nullptr); /// Remove all blocks that can not be reached from the function's entry. /// diff --git a/llvm/lib/CodeGen/WinEHPrepare.cpp b/llvm/lib/CodeGen/WinEHPrepare.cpp --- a/llvm/lib/CodeGen/WinEHPrepare.cpp +++ b/llvm/lib/CodeGen/WinEHPrepare.cpp @@ -972,8 +972,9 @@ // This call site was not part of this funclet, remove it. if (isa(CB)) { - // Remove the unwind edge if it was an invoke. - removeUnwindEdge(BB); + // First remove the unwind edge and convert the invoke to a + // call. Then...delete the call. + removeUnwindEdge(BB, RemoveUnwindEdgeMode::Normal); // Get a pointer to the new call. BasicBlock::iterator CallI = std::prev(BB->getTerminator()->getIterator()); @@ -1007,7 +1008,10 @@ // Invokes within a cleanuppad for the MSVC++ personality never // transfer control to their unwind edge: the personality will // terminate the program. - removeUnwindEdge(BB); + // + // TODO: switch this to RemoveUnwindEdgeMode::Unwindabort when + // adding WinEH unwindabort support. + removeUnwindEdge(BB, RemoveUnwindEdgeMode::Normal); } } } diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp --- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp +++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp @@ -1915,7 +1915,8 @@ auto CheckForNoUnwind = [&](Instruction &I) { if (!I.mayThrow()) return true; - + if (auto *CI = dyn_cast(&I); CI && CI->isUnwindAbort()) + return true; if (const auto *CB = dyn_cast(&I)) { const auto &NoUnwindAA = A.getAAFor( *this, IRPosition::callsite_function(*CB), DepClassTy::REQUIRED); @@ -3248,7 +3249,13 @@ return ChangeStatus::UNCHANGED; auto CheckForWillReturn = [&](Instruction &I) { - IRPosition IPos = IRPosition::callsite_function(cast(I)); + CallBase &CB = cast(I); + // If the call is marked unwindabort, an unwind may be converted into an + // abort, so we can't deduce willreturn for the containing function. + if (CB.isUnwindAbort()) + return false; + + IRPosition IPos = IRPosition::callsite_function(CB); const auto &WillReturnAA = A.getAAFor(*this, IPos, DepClassTy::REQUIRED); if (WillReturnAA.isKnownWillReturn()) diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp --- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp +++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp @@ -1382,6 +1382,8 @@ if (!I.mayThrow()) return false; if (const auto *CI = dyn_cast(&I)) { + if (CI->isUnwindAbort()) + return false; if (Function *Callee = CI->getCalledFunction()) { // I is a may-throw call to a function inside our SCC. This doesn't // invalidate our current working assumption that the SCC is no-throw; we @@ -1603,9 +1605,17 @@ return false; // If there are no loops, then the function is willreturn if all calls in - // it are willreturn. + // it are willreturn but not unwindabort. return all_of(instructions(F), [](const Instruction &I) { - return I.willReturn(); + if (!I.willReturn()) + return false; + // If the call is marked unwindabort, an unwind may be converted into an + // abort, so we can't deduce willreturn for the containing function. + if (const CallBase *Call = dyn_cast(&I)) { + if (Call->isUnwindAbort()) + return false; + } + return true; }); } diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp --- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp +++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp @@ -22,6 +22,7 @@ #include "llvm/Analysis/AliasAnalysis.h" #include "llvm/Analysis/AssumeBundleQueries.h" #include "llvm/Analysis/AssumptionCache.h" +#include "llvm/Analysis/EHPersonalities.h" #include "llvm/Analysis/InstructionSimplify.h" #include "llvm/Analysis/Loads.h" #include "llvm/Analysis/MemoryBuiltins.h" @@ -1151,13 +1152,23 @@ if (Value *FreedOp = getFreedOperand(&CI, &TLI)) return visitFree(CI, FreedOp); - // If the caller function (i.e. us, the function that contains this CallInst) - // is nounwind, mark the call as nounwind, even if the callee isn't. - if (CI.getFunction()->doesNotThrow() && !CI.doesNotThrow()) { + // If the caller function (i.e. the function containing this CallInst) is + // nounwind, and the call is not unwindabort, mark the call as nounwind, even + // when the callee function isn't. + if (CI.getFunction()->doesNotThrow() && !CI.isUnwindAbort() && + !CI.doesNotThrow()) { CI.setDoesNotThrow(); return &CI; } + // If the call is unwindabort and nounwind (and if nounwind _actually_ means + // there can't be any unwinds...), then we can remove the 'unwindabort'. + if (CI.isUnwindAbort() && CI.doesNotThrow() && + canSimplifyInvokeNoUnwind(CI.getFunction())) { + CI.setUnwindAbort(false); + return &CI; + } + IntrinsicInst *II = dyn_cast(&CI); if (!II) return visitCallBase(CI); diff --git a/llvm/lib/Transforms/Utils/EscapeEnumerator.cpp b/llvm/lib/Transforms/Utils/EscapeEnumerator.cpp --- a/llvm/lib/Transforms/Utils/EscapeEnumerator.cpp +++ b/llvm/lib/Transforms/Utils/EscapeEnumerator.cpp @@ -31,14 +31,15 @@ if (Done) return nullptr; - // Find all 'return', 'resume', and 'unwind' instructions. + // Find all 'return', and 'resume' (but not 'resume unwindabort') + // instructions; these instructions may transfer control to the calling + // function. while (StateBB != StateE) { BasicBlock *CurBB = &*StateBB++; - // Branches and invokes do not escape, only unwind, resume, and return - // do. Instruction *TI = CurBB->getTerminator(); - if (!isa(TI) && !isa(TI)) + if (!(isa(TI) || + (isa(TI) && !cast(TI)->isUnwindAbort()))) continue; if (CallInst *CI = CurBB->getTerminatingMustTailCall()) @@ -55,13 +56,14 @@ if (F.doesNotThrow()) return nullptr; - // Find all 'call' instructions that may throw. - // We cannot tranform calls with musttail tag. + // Find all 'call' instructions that may unwind to the calling function. + // We cannot tranform calls with musttail tag, but it is handled above. SmallVector Calls; for (BasicBlock &BB : F) for (Instruction &II : BB) if (CallInst *CI = dyn_cast(&II)) - if (!CI->doesNotThrow() && !CI->isMustTailCall()) + if (!CI->isUnwindAbort() && !CI->doesNotThrow() && + !CI->isMustTailCall()) Calls.push_back(CI); if (Calls.empty()) diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp --- a/llvm/lib/Transforms/Utils/InlineFunction.cpp +++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp @@ -2044,9 +2044,8 @@ } } - // If the call to the callee cannot throw, set the 'nounwind' flag on any - // calls that we inline. bool MarkNoUnwind = CB.doesNotThrow(); + bool MarkUnwindAbort = CB.isUnwindAbort(); BasicBlock *OrigBB = CB.getParent(); Function *Caller = OrigBB->getParent(); @@ -2399,6 +2398,11 @@ for (Function::iterator BB = FirstNewBlock, E = Caller->end(); BB != E; ++BB) { for (Instruction &I : llvm::make_early_inc_range(*BB)) { + if (ResumeInst *RI = dyn_cast(&I)) { + if (MarkUnwindAbort) + RI->setUnwindAbort(); + } + CallInst *CI = dyn_cast(&I); if (!CI) continue; @@ -2460,12 +2464,17 @@ CI->setTailCallKind(ChildTCK); InlinedMustTailCalls |= CI->isMustTailCall(); - // Call sites inlined through a 'nounwind' call site should be - // 'nounwind' as well. However, avoid marking call sites explicitly - // where possible. This helps expose more opportunities for CSE after - // inlining, commonly when the callee is an intrinsic. - if (MarkNoUnwind && !CI->doesNotThrow()) + // Call sites inlined through a 'nounwind' call should be marked + // 'nounwind', unless the inner call was unwindabort (in that case, the + // fact that the outer fn can't throw does _not_ imply the inner + // function can't throw!) + if (MarkNoUnwind && !CI->isUnwindAbort() && !CI->doesNotThrow()) CI->setDoesNotThrow(); + + // Calls inlined through an 'unwindabort' call site should be marked + // 'unwindabort'. + if (MarkUnwindAbort) + CI->setUnwindAbort(); } } } diff --git a/llvm/lib/Transforms/Utils/Local.cpp b/llvm/lib/Transforms/Utils/Local.cpp --- a/llvm/lib/Transforms/Utils/Local.cpp +++ b/llvm/lib/Transforms/Utils/Local.cpp @@ -2557,16 +2557,28 @@ return Changed; } -Instruction *llvm::removeUnwindEdge(BasicBlock *BB, DomTreeUpdater *DTU) { +void llvm::removeUnwindEdge(BasicBlock *BB, RemoveUnwindEdgeMode Mode, + DomTreeUpdater *DTU) { Instruction *TI = BB->getTerminator(); - if (auto *II = dyn_cast(TI)) - return changeToCall(II, DTU); + if (auto *II = dyn_cast(TI)) { + CallInst *CI = changeToCall(II, DTU); + if (Mode == RemoveUnwindEdgeMode::Unwindabort) + CI->setUnwindAbort(true); + if (Mode == RemoveUnwindEdgeMode::Nounwind) + CI->setDoesNotThrow(); + return; + } Instruction *NewTI; BasicBlock *UnwindDest; if (auto *CRI = dyn_cast(TI)) { + // TODO: With Mode==Unwindabort, we should delete entire cleanup funclet, by + // recursively deleting unwind edges _to_ this cleanup. + // + // TODO: With Mode==Nounwind, we should replace the cleanupret with + // 'unreachable'. NewTI = CleanupReturnInst::Create(CRI->getCleanupPad(), nullptr, CRI); UnwindDest = CRI->getUnwindDest(); } else if (auto *CatchSwitch = dyn_cast(TI)) { @@ -2576,6 +2588,13 @@ for (BasicBlock *PadBB : CatchSwitch->handlers()) NewCatchSwitch->addHandler(PadBB); + if (Mode == RemoveUnwindEdgeMode::Unwindabort) { + NewCatchSwitch->setUnwindAbort(true); + } else if (Mode == RemoveUnwindEdgeMode::Nounwind) { + // TODO: Currently we cannot annotate a catchswitch that cannot unwind, as + // it doesn't have a 'nounwind' attribute. Thus, it gets marked 'unwind to + // parent instead, and we lose the knowledge that it should be nounwind. + } NewTI = NewCatchSwitch; UnwindDest = CatchSwitch->getUnwindDest(); } else { @@ -2589,7 +2608,6 @@ TI->eraseFromParent(); if (DTU) DTU->applyUpdates({{DominatorTree::Delete, BB, UnwindDest}}); - return NewTI; } /// removeUnreachableBlocks - Remove blocks that are not reachable, even diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp --- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp +++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp @@ -4886,7 +4886,10 @@ for (BasicBlock *Pred : llvm::make_early_inc_range(predecessors(TrivialBB))) { - removeUnwindEdge(Pred, DTU); + removeUnwindEdge(Pred, + RI->isUnwindAbort() ? RemoveUnwindEdgeMode::Unwindabort + : RemoveUnwindEdgeMode::Normal, + DTU); ++NumInvokes; } @@ -4922,7 +4925,10 @@ // Turn all invokes that unwind here into calls and delete the basic block. for (BasicBlock *Pred : llvm::make_early_inc_range(predecessors(BB))) { - removeUnwindEdge(Pred, DTU); + removeUnwindEdge(Pred, + RI->isUnwindAbort() ? RemoveUnwindEdgeMode::Unwindabort + : RemoveUnwindEdgeMode::Normal, + DTU); ++NumInvokes; } @@ -5023,13 +5029,17 @@ // We use make_early_inc_range here because we will remove all predecessors. for (BasicBlock *PredBB : llvm::make_early_inc_range(predecessors(BB))) { if (UnwindDest == nullptr) { + // if cleanupret was previously unwinding to caller, remove the unwind + // edge for the predecessor, such that it will unwind to caller directly. if (DTU) { DTU->applyUpdates(Updates); Updates.clear(); } - removeUnwindEdge(PredBB, DTU); + removeUnwindEdge(PredBB, RemoveUnwindEdgeMode::Normal, DTU); ++NumInvokes; } else { + // Otherwise, propagate the original unwind-destination into the + // predecessor. BB->removePredecessor(PredBB); Instruction *TI = PredBB->getTerminator(); TI->replaceUsesOfWith(BB, UnwindDest); @@ -5184,9 +5194,7 @@ DTU->applyUpdates(Updates); Updates.clear(); } - auto *CI = cast(removeUnwindEdge(TI->getParent(), DTU)); - if (!CI->doesNotThrow()) - CI->setDoesNotThrow(); + removeUnwindEdge(TI->getParent(), RemoveUnwindEdgeMode::Nounwind, DTU); Changed = true; } } else if (auto *CSI = dyn_cast(TI)) { @@ -5195,7 +5203,7 @@ DTU->applyUpdates(Updates); Updates.clear(); } - removeUnwindEdge(TI->getParent(), DTU); + removeUnwindEdge(TI->getParent(), RemoveUnwindEdgeMode::Nounwind, DTU); Changed = true; continue; } @@ -5213,6 +5221,7 @@ if (DTU) Updates.push_back({DominatorTree::Delete, Predecessor, BB}); if (CSI->getNumHandlers() == 0) { + // No switch clauses left: the catchswitch will always unwind. if (CSI->hasUnwindDest()) { // Redirect all predecessors of the block containing CatchSwitchInst // to instead branch to the CatchSwitchInst's unwind destination. @@ -5227,14 +5236,20 @@ } Predecessor->replaceAllUsesWith(CSI->getUnwindDest()); } else { - // Rewrite all preds to unwind to caller (or from invoke to call). + // Unwind destination is not a block: rewrite all preds to the + // appropriate target ('unwind to caller' or unwindabort). This will + // e.g. rewrite 'invoke' to 'call'. if (DTU) { DTU->applyUpdates(Updates); Updates.clear(); } SmallVector EHPreds(predecessors(Predecessor)); for (BasicBlock *EHPred : EHPreds) - removeUnwindEdge(EHPred, DTU); + removeUnwindEdge(EHPred, + CSI->isUnwindAbort() + ? RemoveUnwindEdgeMode::Unwindabort + : RemoveUnwindEdgeMode::Normal, + DTU); } // The catchswitch is no longer reachable. new UnreachableInst(CSI->getContext(), CSI); diff --git a/llvm/test/Instrumentation/ThreadSanitizer/eh.ll b/llvm/test/Instrumentation/ThreadSanitizer/eh.ll --- a/llvm/test/Instrumentation/ThreadSanitizer/eh.ll +++ b/llvm/test/Instrumentation/ThreadSanitizer/eh.ll @@ -1,3 +1,4 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py ; RUN: opt < %s -passes=tsan -S | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-EXC ; RUN: opt < %s -passes=tsan -S -tsan-handle-cxx-exceptions=0 | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NOEXC @@ -7,51 +8,153 @@ declare void @cannot_throw() nounwind define i32 @func1() sanitize_thread { +; CHECK-EXC-LABEL: @func1( +; CHECK-EXC-NEXT: [[TMP1:%.*]] = call ptr @llvm.returnaddress(i32 0) +; CHECK-EXC-NEXT: call void @__tsan_func_entry(ptr [[TMP1]]) +; CHECK-EXC-NEXT: invoke void @can_throw() +; CHECK-EXC-NEXT: to label [[DOTNOEXC:%.*]] unwind label [[TSAN_CLEANUP:%.*]] +; CHECK-EXC: .noexc: +; CHECK-EXC-NEXT: call void @__tsan_func_exit() +; CHECK-EXC-NEXT: ret i32 0 +; CHECK-EXC: tsan_cleanup: +; CHECK-EXC-NEXT: [[CLEANUP_LPAD:%.*]] = landingpad { ptr, i32 } +; CHECK-EXC-NEXT: cleanup +; CHECK-EXC-NEXT: call void @__tsan_func_exit() +; CHECK-EXC-NEXT: resume { ptr, i32 } [[CLEANUP_LPAD]] +; +; CHECK-NOEXC-LABEL: @func1( +; CHECK-NOEXC-NEXT: [[TMP1:%.*]] = call ptr @llvm.returnaddress(i32 0) +; CHECK-NOEXC-NEXT: call void @__tsan_func_entry(ptr [[TMP1]]) +; CHECK-NOEXC-NEXT: call void @can_throw() +; CHECK-NOEXC-NEXT: call void @__tsan_func_exit() +; CHECK-NOEXC-NEXT: ret i32 0 +; call void @can_throw() ret i32 0 - ; CHECK-EXC: define i32 @func1() - ; CHECK-EXC: call void @__tsan_func_entry - ; CHECK-EXC: invoke void @can_throw() - ; CHECK-EXC: .noexc: - ; CHECK-EXC: call void @__tsan_func_exit() - ; CHECK-EXC: ret i32 0 - ; CHECK-EXC: tsan_cleanup: - ; CHECK-EXC: call void @__tsan_func_exit() - ; CHECK-EXC: resume - ; CHECK-NOEXC: define i32 @func1() - ; CHECK-NOEXC: call void @__tsan_func_entry - ; CHECK-NOEXC: call void @can_throw() - ; CHECK-NOEXC: call void @__tsan_func_exit() - ; CHECK-NOEXC: ret i32 0 } define i32 @func2() sanitize_thread { +; CHECK-LABEL: @func2( +; CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.returnaddress(i32 0) +; CHECK-NEXT: call void @__tsan_func_entry(ptr [[TMP1]]) +; CHECK-NEXT: call void @cannot_throw() +; CHECK-NEXT: call void @__tsan_func_exit() +; CHECK-NEXT: ret i32 0 +; call void @cannot_throw() ret i32 0 - ; CHECK: define i32 @func2() - ; CHECK: call void @__tsan_func_entry - ; CHECK: call void @cannot_throw() - ; CHECK: call void @__tsan_func_exit() - ; CHECK: ret i32 0 } define i32 @func3(ptr %p) sanitize_thread { +; CHECK-LABEL: @func3( +; CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.returnaddress(i32 0) +; CHECK-NEXT: call void @__tsan_func_entry(ptr [[TMP1]]) +; CHECK-NEXT: call void @__tsan_read4(ptr [[P:%.*]]) +; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[P]], align 4 +; CHECK-NEXT: call void @__tsan_func_exit() +; CHECK-NEXT: ret i32 [[A]] +; %a = load i32, ptr %p ret i32 %a - ; CHECK: define i32 @func3(ptr %p) - ; CHECK: call void @__tsan_func_entry - ; CHECK: call void @__tsan_read4 - ; CHECK: %a = load i32, ptr %p - ; CHECK: call void @__tsan_func_exit() - ; CHECK: ret i32 %a } define i32 @func4() sanitize_thread nounwind { +; CHECK-LABEL: @func4( +; CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.returnaddress(i32 0) +; CHECK-NEXT: call void @__tsan_func_entry(ptr [[TMP1]]) +; CHECK-NEXT: call void @can_throw() +; CHECK-NEXT: call void @__tsan_func_exit() +; CHECK-NEXT: ret i32 0 +; call void @can_throw() ret i32 0 - ; CHECK: define i32 @func4() - ; CHECK: call void @__tsan_func_entry - ; CHECK: call void @can_throw() - ; CHECK: call void @__tsan_func_exit() - ; CHECK: ret i32 0 +} + +define i32 @func5() sanitize_thread personality ptr @__gxx_personality_v0 { +; CHECK-LABEL: @func5( +; CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.returnaddress(i32 0) +; CHECK-NEXT: call void @__tsan_func_entry(ptr [[TMP1]]) +; CHECK-NEXT: call unwindabort void @can_throw() +; CHECK-NEXT: call void @__tsan_func_exit() +; CHECK-NEXT: ret i32 0 +; + call unwindabort void @can_throw() + ret i32 0 +} + +declare i1 @maybe_raise_exception() uwtable noinline willreturn +declare i32 @__gxx_personality_v0(...) + +define void @invoke_test() sanitize_thread personality ptr @__gxx_personality_v0 { +; CHECK-LABEL: @invoke_test( +; CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.returnaddress(i32 0) +; CHECK-NEXT: call void @__tsan_func_entry(ptr [[TMP1]]) +; CHECK-NEXT: [[TMP2:%.*]] = invoke i1 @maybe_raise_exception() +; CHECK-NEXT: to label [[N:%.*]] unwind label [[F:%.*]] +; CHECK: N: +; CHECK-NEXT: call void @__tsan_func_exit() +; CHECK-NEXT: ret void +; CHECK: F: +; CHECK-NEXT: [[VAL:%.*]] = landingpad { ptr, i32 } +; CHECK-NEXT: catch ptr null +; CHECK-NEXT: call void @__tsan_func_exit() +; CHECK-NEXT: ret void +; + invoke i1 @maybe_raise_exception() + to label %N unwind label %F + N: + ret void + F: + %val = landingpad { ptr, i32 } + catch ptr null + ret void +} + +define void @invoke_test2() sanitize_thread personality ptr @__gxx_personality_v0 { +; CHECK-LABEL: @invoke_test2( +; CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.returnaddress(i32 0) +; CHECK-NEXT: call void @__tsan_func_entry(ptr [[TMP1]]) +; CHECK-NEXT: [[TMP2:%.*]] = invoke i1 @maybe_raise_exception() +; CHECK-NEXT: to label [[N:%.*]] unwind label [[F:%.*]] +; CHECK: N: +; CHECK-NEXT: call void @__tsan_func_exit() +; CHECK-NEXT: ret void +; CHECK: F: +; CHECK-NEXT: [[VAL:%.*]] = landingpad { ptr, i32 } +; CHECK-NEXT: cleanup +; CHECK-NEXT: call void @__tsan_func_exit() +; CHECK-NEXT: resume { ptr, i32 } [[VAL]] +; + invoke i1 @maybe_raise_exception() + to label %N unwind label %F + N: + ret void + F: + %val = landingpad { ptr, i32 } cleanup + + resume {ptr, i32 } %val +} + +define void @invoke_test3() sanitize_thread personality ptr @__gxx_personality_v0 { +; CHECK-LABEL: @invoke_test3( +; CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.returnaddress(i32 0) +; CHECK-NEXT: call void @__tsan_func_entry(ptr [[TMP1]]) +; CHECK-NEXT: [[TMP2:%.*]] = invoke i1 @maybe_raise_exception() +; CHECK-NEXT: to label [[N:%.*]] unwind label [[F:%.*]] +; CHECK: N: +; CHECK-NEXT: call void @__tsan_func_exit() +; CHECK-NEXT: ret void +; CHECK: F: +; CHECK-NEXT: [[VAL:%.*]] = landingpad { ptr, i32 } +; CHECK-NEXT: cleanup +; CHECK-NEXT: resume unwindabort { ptr, i32 } [[VAL]] +; + invoke i1 @maybe_raise_exception() + to label %N unwind label %F + N: + ret void + F: + %val = landingpad { ptr, i32 } cleanup + + resume unwindabort {ptr, i32 } %val } diff --git a/llvm/test/Transforms/Attributor/nounwind.ll b/llvm/test/Transforms/Attributor/nounwind.ll --- a/llvm/test/Transforms/Attributor/nounwind.ll +++ b/llvm/test/Transforms/Attributor/nounwind.ll @@ -138,6 +138,79 @@ ret i32 %catch_thing_call } +; TEST 7: test that it's possible to deduce nounwind even when the +; function contains an invoke to a throwing function. Unlike the +; above test, the only potentially-unwinding calls are through +; 'invoke'. +define i32 @test_invoke_nounwind(i1 zeroext %val) personality ptr @__gxx_personality_v0 { +; TUNIT: Function Attrs: noreturn nounwind +; TUNIT-LABEL: define {{[^@]+}}@test_invoke_nounwind +; TUNIT-SAME: (i1 zeroext [[VAL:%.*]]) #[[ATTR2:[0-9]+]] personality ptr @__gxx_personality_v0 { +; TUNIT-NEXT: entry: +; TUNIT-NEXT: [[CALL:%.*]] = invoke noundef i32 @maybe_throw(i1 [[VAL]]) +; TUNIT-NEXT: to label [[TRY_CONT:%.*]] unwind label [[LPAD:%.*]] +; TUNIT: lpad: +; TUNIT-NEXT: [[TMP0:%.*]] = landingpad { ptr, i32 } +; TUNIT-NEXT: catch ptr null +; TUNIT-NEXT: [[TMP1:%.*]] = extractvalue { ptr, i32 } [[TMP0]], 0 +; TUNIT-NEXT: [[TMP2:%.*]] = tail call ptr @__cxa_begin_catch(ptr [[TMP1]]) #[[ATTR3:[0-9]+]] +; TUNIT-NEXT: tail call void @abort() #[[ATTR2]] +; TUNIT-NEXT: unreachable +; TUNIT: try.cont: +; TUNIT-NEXT: unreachable +; +; CGSCC: Function Attrs: noreturn nounwind +; CGSCC-LABEL: define {{[^@]+}}@test_invoke_nounwind +; CGSCC-SAME: (i1 noundef zeroext [[VAL:%.*]]) #[[ATTR1:[0-9]+]] personality ptr @__gxx_personality_v0 { +; CGSCC-NEXT: entry: +; CGSCC-NEXT: [[CALL:%.*]] = invoke noundef i32 @maybe_throw(i1 noundef [[VAL]]) +; CGSCC-NEXT: to label [[TRY_CONT:%.*]] unwind label [[LPAD:%.*]] +; CGSCC: lpad: +; CGSCC-NEXT: [[TMP0:%.*]] = landingpad { ptr, i32 } +; CGSCC-NEXT: catch ptr null +; CGSCC-NEXT: [[TMP1:%.*]] = extractvalue { ptr, i32 } [[TMP0]], 0 +; CGSCC-NEXT: [[TMP2:%.*]] = tail call ptr @__cxa_begin_catch(ptr [[TMP1]]) #[[ATTR2:[0-9]+]] +; CGSCC-NEXT: tail call void @abort() #[[ATTR1]] +; CGSCC-NEXT: unreachable +; CGSCC: try.cont: +; CGSCC-NEXT: unreachable +; +entry: + %call = invoke noundef i32 @maybe_throw(i1 %val) + to label %try.cont unwind label %lpad + +lpad: ; preds = %entry + %0 = landingpad { ptr, i32 } + catch ptr null + %1 = extractvalue { ptr, i32 } %0, 0 + %2 = tail call ptr @__cxa_begin_catch(ptr %1) nounwind + tail call void @abort() noreturn nounwind + unreachable + +try.cont: ; preds = %entry + unreachable +} + +; TEST 8: test that a function body with 'call unwindabort' can be +; deduced as nounwind. +define i32 @test_unwindabort(i1 zeroext %val) personality ptr @__gxx_personality_v0 { +; TUNIT: Function Attrs: nounwind +; TUNIT-LABEL: define {{[^@]+}}@test_unwindabort +; TUNIT-SAME: (i1 zeroext [[VAL:%.*]]) #[[ATTR3]] personality ptr @__gxx_personality_v0 { +; TUNIT-NEXT: [[CALL:%.*]] = call unwindabort i32 @maybe_throw(i1 [[VAL]]) +; TUNIT-NEXT: ret i32 -1 +; +; CGSCC: Function Attrs: nounwind +; CGSCC-LABEL: define {{[^@]+}}@test_unwindabort +; CGSCC-SAME: (i1 noundef zeroext [[VAL:%.*]]) #[[ATTR2]] personality ptr @__gxx_personality_v0 { +; CGSCC-NEXT: [[CALL:%.*]] = call unwindabort noundef i32 @maybe_throw(i1 noundef [[VAL]]) +; CGSCC-NEXT: ret i32 [[CALL]] +; + %call = call unwindabort i32 @maybe_throw(i1 %val) + ret i32 %call +} + +declare void @abort() local_unnamed_addr noreturn nounwind declare i32 @__gxx_personality_v0(...) @@ -147,6 +220,10 @@ ;. ; TUNIT: attributes #[[ATTR0]] = { nofree norecurse nosync nounwind willreturn memory(none) } ; TUNIT: attributes #[[ATTR1]] = { nofree nosync nounwind willreturn memory(none) } +; TUNIT: attributes #[[ATTR2]] = { noreturn nounwind } +; TUNIT: attributes #[[ATTR3]] = { nounwind } ;. ; CGSCC: attributes #[[ATTR0]] = { nofree norecurse nosync nounwind willreturn memory(none) } +; CGSCC: attributes #[[ATTR1]] = { noreturn nounwind } +; CGSCC: attributes #[[ATTR2]] = { nounwind } ;. diff --git a/llvm/test/Transforms/Attributor/willreturn.ll b/llvm/test/Transforms/Attributor/willreturn.ll --- a/llvm/test/Transforms/Attributor/willreturn.ll +++ b/llvm/test/Transforms/Attributor/willreturn.ll @@ -453,6 +453,37 @@ ret void } +; Positive test: calling a willreturn function that may unwind is OK: +; it'll either unwind or return. +define void @call_unwind_test() noinline uwtable { +; TUNIT: Function Attrs: noinline willreturn uwtable +; TUNIT-LABEL: define {{[^@]+}}@call_unwind_test +; TUNIT-SAME: () #[[ATTR11:[0-9]+]] { +; TUNIT-NEXT: [[TMP1:%.*]] = call i1 @maybe_raise_exception() #[[ATTR27]] +; TUNIT-NEXT: ret void +; +; CGSCC: Function Attrs: noinline willreturn uwtable +; CGSCC-LABEL: define {{[^@]+}}@call_unwind_test +; CGSCC-SAME: () #[[ATTR11:[0-9]+]] { +; CGSCC-NEXT: [[TMP1:%.*]] = call i1 @maybe_raise_exception() #[[ATTR28]] +; CGSCC-NEXT: ret void +; + call i1 @maybe_raise_exception() + ret void +} + +; Negative test: a 'call unwindabort' is _not_ OK: it may abort. +define void @callabort_unwind_test() #0 personality i32 (...)* @__gxx_personality_v0 { +; CHECK: Function Attrs: noinline nounwind uwtable +; CHECK-LABEL: define {{[^@]+}}@callabort_unwind_test +; CHECK-SAME: () #[[ATTR7]] personality i32 (...)* @__gxx_personality_v0 { +; CHECK-NEXT: [[TMP1:%.*]] = call unwindabort i1 @maybe_raise_exception() +; CHECK-NEXT: ret void +; + call unwindabort i1 @maybe_raise_exception() + ret void +} + declare i32 @__gxx_personality_v0(...) @@ -1278,6 +1309,7 @@ ret void } + attributes #0 = { nounwind uwtable noinline } attributes #1 = { uwtable noinline } ;. @@ -1292,7 +1324,7 @@ ; TUNIT: attributes #[[ATTR8:[0-9]+]] = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } ; TUNIT: attributes #[[ATTR9:[0-9]+]] = { norecurse willreturn } ; TUNIT: attributes #[[ATTR10]] = { noinline nounwind willreturn uwtable } -; TUNIT: attributes #[[ATTR11:[0-9]+]] = { noinline willreturn uwtable } +; TUNIT: attributes #[[ATTR11]] = { noinline willreturn uwtable } ; TUNIT: attributes #[[ATTR12]] = { nounwind willreturn } ; TUNIT: attributes #[[ATTR13]] = { nofree noinline norecurse nosync nounwind willreturn memory(argmem: read) uwtable } ; TUNIT: attributes #[[ATTR14]] = { nofree noinline norecurse nosync nounwind memory(argmem: read) uwtable } @@ -1322,7 +1354,7 @@ ; CGSCC: attributes #[[ATTR8:[0-9]+]] = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } ; CGSCC: attributes #[[ATTR9:[0-9]+]] = { norecurse willreturn } ; CGSCC: attributes #[[ATTR10]] = { noinline nounwind willreturn uwtable } -; CGSCC: attributes #[[ATTR11:[0-9]+]] = { noinline willreturn uwtable } +; CGSCC: attributes #[[ATTR11]] = { noinline willreturn uwtable } ; CGSCC: attributes #[[ATTR12]] = { nounwind willreturn } ; CGSCC: attributes #[[ATTR13]] = { nofree noinline norecurse nosync nounwind willreturn memory(argmem: read) uwtable } ; CGSCC: attributes #[[ATTR14]] = { nofree noinline norecurse nosync nounwind memory(argmem: read) uwtable } diff --git a/llvm/test/Transforms/FunctionAttrs/nounwind.ll b/llvm/test/Transforms/FunctionAttrs/nounwind.ll --- a/llvm/test/Transforms/FunctionAttrs/nounwind.ll +++ b/llvm/test/Transforms/FunctionAttrs/nounwind.ll @@ -78,7 +78,12 @@ declare void @__cxa_rethrow() -; TEST 6 - catch +; TEST 6 - catch. +; +; In this test, it is not possible to deduce nounwind for +; "catch_thing", because the call to @__cxa_end_catch is not marked +; nounwind. TODO: _could_ it be marked nounwind? + ; int catch_thing() { ; try { ; int a = doThing(true); @@ -125,6 +130,58 @@ ret i32 %catch_thing_call } +; TEST 7: test that it's possible to deduce nounwind even when the +; function contains an invoke to a throwing function. Unlike the +; above test, the only potentially-unwinding calls are through +; 'invoke'. +define i32 @test_invoke_nounwind(i1 zeroext %val) personality ptr @__gxx_personality_v0 { +; CHECK: Function Attrs: noreturn nounwind +; CHECK-LABEL: define {{[^@]+}}@test_invoke_nounwind +; CHECK-SAME: (i1 zeroext [[VAL:%.*]]) #[[ATTR2:[0-9]+]] personality ptr @__gxx_personality_v0 { +; CHECK-NEXT: entry: +; CHECK-NEXT: [[CALL:%.*]] = invoke noundef i32 @maybe_throw(i1 [[VAL]]) +; CHECK-NEXT: to label [[TRY_CONT:%.*]] unwind label [[LPAD:%.*]] +; CHECK: lpad: +; CHECK-NEXT: [[TMP0:%.*]] = landingpad { ptr, i32 } +; CHECK-NEXT: catch ptr null +; CHECK-NEXT: [[TMP1:%.*]] = extractvalue { ptr, i32 } [[TMP0]], 0 +; CHECK-NEXT: [[TMP2:%.*]] = tail call ptr @__cxa_begin_catch(ptr [[TMP1]]) #[[ATTR3:[0-9]+]] +; CHECK-NEXT: tail call void @abort() #[[ATTR2]] +; CHECK-NEXT: unreachable +; CHECK: try.cont: +; CHECK-NEXT: unreachable +; +entry: + %call = invoke noundef i32 @maybe_throw(i1 %val) + to label %try.cont unwind label %lpad + +lpad: ; preds = %entry + %0 = landingpad { ptr, i32 } + catch ptr null + %1 = extractvalue { ptr, i32 } %0, 0 + %2 = tail call ptr @__cxa_begin_catch(ptr %1) nounwind + tail call void @abort() noreturn nounwind + unreachable + +try.cont: ; preds = %entry + unreachable +} + +; TEST 8: test that a function body with 'call unwindabort' can be +; deduced as nounwind. +define i32 @test_unwindabort(i1 zeroext %val) personality ptr @__gxx_personality_v0 { +; CHECK: Function Attrs: nounwind +; CHECK-LABEL: define {{[^@]+}}@test_unwindabort +; CHECK-SAME: (i1 zeroext [[VAL:%.*]]) #[[ATTR3]] personality ptr @__gxx_personality_v0 { +; CHECK-NEXT: [[CALL:%.*]] = call unwindabort i32 @maybe_throw(i1 [[VAL]]) +; CHECK-NEXT: ret i32 [[CALL]] +; + %call = call unwindabort i32 @maybe_throw(i1 %val) + ret i32 %call +} + +declare void @abort() local_unnamed_addr noreturn nounwind + declare i32 @__gxx_personality_v0(...) diff --git a/llvm/test/Transforms/FunctionAttrs/willreturn.ll b/llvm/test/Transforms/FunctionAttrs/willreturn.ll --- a/llvm/test/Transforms/FunctionAttrs/willreturn.ll +++ b/llvm/test/Transforms/FunctionAttrs/willreturn.ll @@ -222,3 +222,51 @@ declare i64 @fn_noread() readnone declare void @fn_willreturn() willreturn + + +declare i1 @maybe_raise_exception() uwtable noinline willreturn + +; Positive test: an invoke does not block willreturn +define void @invoke_test() personality ptr @__gxx_personality_v0 { +; CHECK: Function Attrs: mustprogress nounwind willreturn +; CHECK-LABEL: @invoke_test( +; CHECK-NEXT: [[TMP1:%.*]] = invoke i1 @maybe_raise_exception() +; CHECK-NEXT: to label [[N:%.*]] unwind label [[F:%.*]] +; CHECK: N: +; CHECK-NEXT: ret void +; CHECK: F: +; CHECK-NEXT: [[VAL:%.*]] = landingpad { ptr, i32 } +; CHECK-NEXT: catch ptr null +; CHECK-NEXT: ret void +; + invoke i1 @maybe_raise_exception() + to label %N unwind label %F + N: + ret void + F: + %val = landingpad { ptr, i32 } + catch ptr null + ret void +} + +; Positive test: calling a willreturn function that may unwind is OK, too. +define void @call_unwind_test() noinline uwtable { +; CHECK: Function Attrs: mustprogress noinline willreturn uwtable +; CHECK-LABEL: @call_unwind_test( +; CHECK-NEXT: [[TMP1:%.*]] = call i1 @maybe_raise_exception() +; CHECK-NEXT: ret void +; + call i1 @maybe_raise_exception() + ret void +} + +; Negative test: a 'call unwindabort' is _not_ OK: it may abort. +define void @callabort_unwind_test() nounwind uwtable noinline personality ptr @__gxx_personality_v0 { +; CHECK: Function Attrs: noinline nounwind uwtable +; CHECK-LABEL: @callabort_unwind_test( +; CHECK-NEXT: [[TMP1:%.*]] = call unwindabort i1 @maybe_raise_exception() +; CHECK-NEXT: ret void +; + call unwindabort i1 @maybe_raise_exception() + ret void +} diff --git a/llvm/test/Transforms/Inline/inline-unwindabort.ll b/llvm/test/Transforms/Inline/inline-unwindabort.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Inline/inline-unwindabort.ll @@ -0,0 +1,86 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals +; RUN: opt < %s -passes='cgscc(inline)' -S | FileCheck %s + +;; Ensure that inlining propagates unwindabort as expected: +;; +;; Unwindabort on a callsite attaches to all calls inlined through it. +;; +;; A nounwind callsite propagates nounwind to calls inlined through +;; it, except that an unwindabort inner call cannot be deduced to be +;; nounwind, as it's unwind doesn't propagate out. +declare void @extern() +declare void @extern_nounwind() nounwind +declare i32 @__gxx_personality_v0(...) + +define internal void @inlined_callee() personality ptr @__gxx_personality_v0 { +entry: + call void @extern() + call void @extern_nounwind() nounwind + call unwindabort void @extern() + call unwindabort void @extern_nounwind() nounwind + ret void +} + +define void @caller() { +; +; CHECK-LABEL: @caller( +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @extern() +; CHECK-NEXT: call void @extern_nounwind() #[[ATTR0:[0-9]+]] +; CHECK-NEXT: call unwindabort void @extern() +; CHECK-NEXT: call unwindabort void @extern_nounwind() #[[ATTR0]] +; CHECK-NEXT: ret void +; +entry: + call void @inlined_callee() + ret void +} + +define void @nounwind_caller() { +; +; CHECK-LABEL: @nounwind_caller( +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @extern() #[[ATTR0]] +; CHECK-NEXT: call void @extern_nounwind() #[[ATTR0]] +; CHECK-NEXT: call unwindabort void @extern() +; CHECK-NEXT: call unwindabort void @extern_nounwind() #[[ATTR0]] +; CHECK-NEXT: ret void +; +entry: + call void @inlined_callee() nounwind + ret void +} + +define void @unwindabort_caller() personality ptr @__gxx_personality_v0 { +; +; CHECK-LABEL: @unwindabort_caller( +; CHECK-NEXT: entry: +; CHECK-NEXT: call unwindabort void @extern() +; CHECK-NEXT: call unwindabort void @extern_nounwind() #[[ATTR0]] +; CHECK-NEXT: call unwindabort void @extern() +; CHECK-NEXT: call unwindabort void @extern_nounwind() #[[ATTR0]] +; CHECK-NEXT: ret void +; +entry: + call unwindabort void @inlined_callee() + ret void +} + +define void @unwindabort_nounwind_caller() personality ptr @__gxx_personality_v0 { +; +; CHECK-LABEL: @unwindabort_nounwind_caller( +; CHECK-NEXT: entry: +; CHECK-NEXT: call unwindabort void @extern() #[[ATTR0]] +; CHECK-NEXT: call unwindabort void @extern_nounwind() #[[ATTR0]] +; CHECK-NEXT: call unwindabort void @extern() +; CHECK-NEXT: call unwindabort void @extern_nounwind() #[[ATTR0]] +; CHECK-NEXT: ret void +; +entry: + call unwindabort void @inlined_callee() nounwind + ret void +} + +;. +; CHECK: attributes #[[ATTR0]] = { nounwind } +;. diff --git a/llvm/test/Transforms/InstCombine/nothrow.ll b/llvm/test/Transforms/InstCombine/nothrow.ll --- a/llvm/test/Transforms/InstCombine/nothrow.ll +++ b/llvm/test/Transforms/InstCombine/nothrow.ll @@ -1,8 +1,36 @@ -; RUN: opt < %s -passes=instcombine -S | not grep call -; rdar://6880732 +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals +; RUN: opt < %s -passes=instcombine -S | FileCheck %s declare double @t1(i32) readonly willreturn +;; The call is marked nounwind, even though callee is not nounwind, as +;; it would be UB to unwind from it through this nounwind +;; function. (And thus, the call can be removed due to being unused +;; and free of side effects.) define void @t2() nounwind { - call double @t1(i32 42) ;; dead call even though callee is not nothrow. +; CHECK: Function Attrs: nounwind +; CHECK-LABEL: @t2( +; CHECK-NEXT: ret void +; + call double @t1(i32 42) ret void } + +;; If a `call unwindabort` is within a nounwind function, that does +;; NOT mean the call itself must be nounwind. Due to the +;; 'unwindabort', the call will not continue to unwind. +define void @t3() nounwind personality ptr @__gxx_personality_v0 { +; CHECK: Function Attrs: nounwind +; CHECK-LABEL: @t3( +; CHECK-NEXT: [[TMP1:%.*]] = call unwindabort double @t1(i32 42) +; CHECK-NEXT: ret void +; + call unwindabort double @t1(i32 42) + ret void +} + +declare i32 @__gxx_personality_v0(...) + +;. +; CHECK: attributes #[[ATTR0:[0-9]+]] = { willreturn memory(read) } +; CHECK: attributes #[[ATTR1:[0-9]+]] = { nounwind } +;. diff --git a/llvm/test/Transforms/InstCombine/unwindabort.ll b/llvm/test/Transforms/InstCombine/unwindabort.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/InstCombine/unwindabort.ll @@ -0,0 +1,27 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; RUN: opt < %s -passes=instcombine -S | FileCheck %s +declare void @nothrow() nounwind +declare i32 @__gxx_personality_v0(...) +declare i32 @_except_handler3(...) + +; Verify that unwindabort is removed when a call is nounwind. +define void @test() personality ptr @__gxx_personality_v0 { +; CHECK-LABEL: @test( +; CHECK-NEXT: call void @nothrow() +; CHECK-NEXT: ret void +; + call unwindabort void @nothrow() + ret void +} + +; ...except we can't remove unwindabort of a nounwind function, if the +; personality handles async exceptions. +define void @testSEH() personality ptr @_except_handler3 { +; CHECK-LABEL: @testSEH( +; CHECK-NEXT: call unwindabort void @nothrow() +; CHECK-NEXT: ret void +; + call unwindabort void @nothrow() + ret void +} + diff --git a/llvm/test/Transforms/SimplifyCFG/invoke_unwind.ll b/llvm/test/Transforms/SimplifyCFG/invoke_unwind.ll --- a/llvm/test/Transforms/SimplifyCFG/invoke_unwind.ll +++ b/llvm/test/Transforms/SimplifyCFG/invoke_unwind.ll @@ -86,6 +86,67 @@ br label %rethrow } + +;; Now test that both simple-resume and common-resume propagate unwindabort properly. +define i32 @test4() personality ptr @__gxx_personality_v0 { +; CHECK-LABEL: @test4( +; CHECK-NEXT: call unwindabort void @bar() +; CHECK-NEXT: ret i32 0 +; + invoke void @bar( ) + to label %1 unwind label %Rethrow + ret i32 0 +Rethrow: + %exn = landingpad {ptr, i32} + cleanup + resume unwindabort {ptr, i32} %exn +} + +define i64 @test5(i1 %cond) personality ptr @__gxx_personality_v0 { +; CHECK-LABEL: @test5( +; CHECK-NEXT: entry: +; CHECK-NEXT: br i1 [[COND:%.*]], label [[BR1:%.*]], label [[BR2:%.*]] +; CHECK: br1: +; CHECK-NEXT: [[CALL1:%.*]] = call unwindabort i64 @dummy1() +; CHECK-NEXT: br label [[INVOKE_CONT:%.*]] +; CHECK: br2: +; CHECK-NEXT: [[CALL2:%.*]] = call unwindabort i64 @dummy2() +; CHECK-NEXT: br label [[INVOKE_CONT]] +; CHECK: invoke.cont: +; CHECK-NEXT: [[C:%.*]] = phi i64 [ [[CALL1]], [[BR1]] ], [ [[CALL2]], [[BR2]] ] +; CHECK-NEXT: ret i64 [[C]] +; +entry: + br i1 %cond, label %br1, label %br2 + +br1: + %call1 = invoke i64 @dummy1() + to label %invoke.cont unwind label %lpad1 + +br2: + %call2 = invoke i64 @dummy2() + to label %invoke.cont unwind label %lpad2 + +invoke.cont: + %c = phi i64 [%call1, %br1], [%call2, %br2] + ret i64 %c + +lpad1: + %0 = landingpad { ptr, i32 } + cleanup + br label %rethrow + +rethrow: + %lp = phi { ptr, i32 } [%0, %lpad1], [%1, %lpad2] + resume unwindabort { ptr, i32 } %lp + +lpad2: + %1 = landingpad { ptr, i32 } + cleanup + br label %rethrow +} + + declare i32 @__gxx_personality_v0(...) ;. ; CHECK: [[PROF0]] = !{!"branch_weights", i32 371} diff --git a/llvm/test/Verifier/musttail-invalid.ll b/llvm/test/Verifier/musttail-invalid.ll --- a/llvm/test/Verifier/musttail-invalid.ll +++ b/llvm/test/Verifier/musttail-invalid.ll @@ -81,3 +81,11 @@ musttail call void asm "ret", ""() ret void } + +declare i32 @testfn() +declare void @person() +define i32 @unwindabort() personality ptr @person { +; CHECK: cannot use musttail with unwindabort + %v = musttail call unwindabort i32 @testfn() + ret i32 %v +}