diff --git a/llvm/include/llvm/IR/EHPersonalities.h b/llvm/include/llvm/IR/EHPersonalities.h --- a/llvm/include/llvm/IR/EHPersonalities.h +++ b/llvm/include/llvm/IR/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 @@ -1107,8 +1107,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()); @@ -1142,7 +1143,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 @@ -2055,7 +2055,8 @@ auto CheckForNoUnwind = [&](Instruction &I) { if (!I.mayThrow(/* IncludePhaseOneUnwind */ true)) return true; - + if (auto *CI = dyn_cast(&I); CI && CI->isUnwindAbort()) + return true; if (const auto *CB = dyn_cast(&I)) { bool IsKnownNoUnwind; return AA::hasAssumedIRAttr( @@ -3348,7 +3349,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); bool IsKnown; if (AA::hasAssumedIRAttr( A, this, IPos, DepClassTy::REQUIRED, IsKnown)) { 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 @@ -1379,6 +1379,8 @@ if (!I.mayThrow(/* IncludePhaseOneUnwind */ true)) 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 @@ -1669,9 +1671,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 @@ -35,6 +35,7 @@ #include "llvm/IR/DataLayout.h" #include "llvm/IR/DebugInfo.h" #include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/EHPersonalities.h" #include "llvm/IR/Function.h" #include "llvm/IR/GlobalVariable.h" #include "llvm/IR/InlineAsm.h" @@ -1401,13 +1402,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 @@ -1971,9 +1971,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(); @@ -2322,6 +2321,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; @@ -2383,12 +2387,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 @@ -2619,16 +2619,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)) { @@ -2638,6 +2650,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 { @@ -2651,7 +2670,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 @@ -4871,7 +4871,10 @@ for (BasicBlock *Pred : llvm::make_early_inc_range(predecessors(TrivialBB))) { - removeUnwindEdge(Pred, DTU); + removeUnwindEdge(Pred, + RI->isUnwindAbort() ? RemoveUnwindEdgeMode::Unwindabort + : RemoveUnwindEdgeMode::Normal, + DTU); ++NumInvokes; } @@ -4907,7 +4910,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; } @@ -5008,13 +5014,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); @@ -5173,9 +5183,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)) { @@ -5184,7 +5192,7 @@ DTU->applyUpdates(Updates); Updates.clear(); } - removeUnwindEdge(TI->getParent(), DTU); + removeUnwindEdge(TI->getParent(), RemoveUnwindEdgeMode::Nounwind, DTU); Changed = true; continue; } @@ -5202,6 +5210,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. @@ -5216,14 +5225,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 noundef [[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 noundef [[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]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) } ; TUNIT: attributes #[[ATTR1]] = { mustprogress nofree nosync nounwind willreturn memory(none) } +; TUNIT: attributes #[[ATTR2]] = { noreturn nounwind } +; TUNIT: attributes #[[ATTR3]] = { nounwind } ;. ; CGSCC: attributes #[[ATTR0]] = { mustprogress 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 @@ -35,9 +35,9 @@ ; TUNIT-NEXT: br i1 [[TMP2]], label [[TMP9:%.*]], label [[TMP3:%.*]] ; TUNIT: 3: ; TUNIT-NEXT: [[TMP4:%.*]] = add nsw i32 [[TMP0]], -1 -; TUNIT-NEXT: [[TMP5:%.*]] = tail call i32 @fib(i32 [[TMP4]]) #[[ATTR27:[0-9]+]] +; TUNIT-NEXT: [[TMP5:%.*]] = tail call i32 @fib(i32 [[TMP4]]) #[[ATTR28:[0-9]+]] ; TUNIT-NEXT: [[TMP6:%.*]] = add nsw i32 [[TMP0]], -2 -; TUNIT-NEXT: [[TMP7:%.*]] = tail call i32 @fib(i32 [[TMP6]]) #[[ATTR27]] +; TUNIT-NEXT: [[TMP7:%.*]] = tail call i32 @fib(i32 [[TMP6]]) #[[ATTR28]] ; TUNIT-NEXT: [[TMP8:%.*]] = add nsw i32 [[TMP7]], [[TMP5]] ; TUNIT-NEXT: ret i32 [[TMP8]] ; TUNIT: 9: @@ -50,9 +50,9 @@ ; CGSCC-NEXT: br i1 [[TMP2]], label [[TMP9:%.*]], label [[TMP3:%.*]] ; CGSCC: 3: ; CGSCC-NEXT: [[TMP4:%.*]] = add nsw i32 [[TMP0]], -1 -; CGSCC-NEXT: [[TMP5:%.*]] = tail call i32 @fib(i32 [[TMP4]]) #[[ATTR19:[0-9]+]] +; CGSCC-NEXT: [[TMP5:%.*]] = tail call i32 @fib(i32 [[TMP4]]) #[[ATTR20:[0-9]+]] ; CGSCC-NEXT: [[TMP6:%.*]] = add nsw i32 [[TMP0]], -2 -; CGSCC-NEXT: [[TMP7:%.*]] = tail call i32 @fib(i32 [[TMP6]]) #[[ATTR19]] +; CGSCC-NEXT: [[TMP7:%.*]] = tail call i32 @fib(i32 [[TMP6]]) #[[ATTR20]] ; CGSCC-NEXT: [[TMP8:%.*]] = add nsw i32 [[TMP7]], [[TMP5]] ; CGSCC-NEXT: ret i32 [[TMP8]] ; CGSCC: 9: @@ -181,8 +181,8 @@ ; CHECK-SAME: (i1 noundef [[C:%.*]]) #[[ATTR4:[0-9]+]] { ; CHECK-NEXT: br i1 [[C]], label [[REC:%.*]], label [[END:%.*]] ; CHECK: rec: -; CHECK-NEXT: call void @sink() #[[ATTR28:[0-9]+]] -; CHECK-NEXT: call void @mutual_recursion2(i1 noundef [[C]]) #[[ATTR29:[0-9]+]] +; CHECK-NEXT: call void @sink() #[[ATTR29:[0-9]+]] +; CHECK-NEXT: call void @mutual_recursion2(i1 noundef [[C]]) #[[ATTR30:[0-9]+]] ; CHECK-NEXT: br label [[END]] ; CHECK: end: ; CHECK-NEXT: ret void @@ -201,7 +201,7 @@ ; CHECK: Function Attrs: nofree noinline nosync nounwind uwtable ; CHECK-LABEL: define {{[^@]+}}@mutual_recursion2 ; CHECK-SAME: (i1 [[C:%.*]]) #[[ATTR4]] { -; CHECK-NEXT: call void @mutual_recursion1(i1 noundef [[C]]) #[[ATTR29]] +; CHECK-NEXT: call void @mutual_recursion1(i1 noundef [[C]]) #[[ATTR30]] ; CHECK-NEXT: ret void ; call void @mutual_recursion1(i1 %c) @@ -295,7 +295,7 @@ ; CHECK: Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(none) uwtable ; CHECK-LABEL: define {{[^@]+}}@call_floor2 ; CHECK-SAME: (float [[A:%.*]]) #[[ATTR0]] { -; CHECK-NEXT: [[C:%.*]] = tail call nofpclass(sub) float @llvm.floor.f32(float [[A]]) #[[ATTR30:[0-9]+]] +; CHECK-NEXT: [[C:%.*]] = tail call nofpclass(sub) float @llvm.floor.f32(float [[A]]) #[[ATTR31:[0-9]+]] ; CHECK-NEXT: ret float [[C]] ; %c = tail call float @llvm.floor.f32(float %a) @@ -315,7 +315,7 @@ ; CHECK: Function Attrs: noinline nounwind uwtable ; CHECK-LABEL: define {{[^@]+}}@call_maybe_noreturn ; CHECK-SAME: () #[[ATTR7]] { -; CHECK-NEXT: tail call void @maybe_noreturn() #[[ATTR31:[0-9]+]] +; CHECK-NEXT: tail call void @maybe_noreturn() #[[ATTR32:[0-9]+]] ; CHECK-NEXT: ret void ; tail call void @maybe_noreturn() @@ -334,7 +334,7 @@ ; CHECK: Function Attrs: mustprogress noinline nounwind willreturn uwtable ; CHECK-LABEL: define {{[^@]+}}@f1 ; CHECK-SAME: () #[[ATTR10:[0-9]+]] { -; CHECK-NEXT: tail call void @will_return() #[[ATTR32:[0-9]+]] +; CHECK-NEXT: tail call void @will_return() #[[ATTR33:[0-9]+]] ; CHECK-NEXT: ret void ; tail call void @will_return() @@ -345,7 +345,7 @@ ; CHECK: Function Attrs: mustprogress noinline nounwind willreturn uwtable ; CHECK-LABEL: define {{[^@]+}}@f2 ; CHECK-SAME: () #[[ATTR10]] { -; CHECK-NEXT: tail call void @f1() #[[ATTR33:[0-9]+]] +; CHECK-NEXT: tail call void @f1() #[[ATTR34:[0-9]+]] ; CHECK-NEXT: ret void ; tail call void @f1() @@ -387,7 +387,7 @@ ; CHECK: Function Attrs: mustprogress nounwind willreturn ; CHECK-LABEL: define {{[^@]+}}@invoke_test ; CHECK-SAME: () #[[ATTR12:[0-9]+]] personality ptr @__gxx_personality_v0 { -; CHECK-NEXT: [[TMP1:%.*]] = invoke i1 @maybe_raise_exception() #[[ATTR32]] +; CHECK-NEXT: [[TMP1:%.*]] = invoke i1 @maybe_raise_exception() #[[ATTR33]] ; CHECK-NEXT: to label [[N:%.*]] unwind label [[F:%.*]] ; CHECK: N: ; CHECK-NEXT: ret void @@ -406,6 +406,31 @@ 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 { +; CHECK: Function Attrs: mustprogress noinline willreturn uwtable +; CHECK-LABEL: define {{[^@]+}}@call_unwind_test +; CHECK-SAME: () #[[ATTR13:[0-9]+]] { +; CHECK-NEXT: [[TMP1:%.*]] = call i1 @maybe_raise_exception() #[[ATTR33]] +; 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() #0 personality i32 (...)* @__gxx_personality_v0 { +; CHECK: Function Attrs: noinline nounwind uwtable +; CHECK-LABEL: define {{[^@]+}}@callabort_unwind_test +; CHECK-SAME: () #[[ATTR7]] personality ptr @__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(...) @@ -422,7 +447,7 @@ define i32 @loop_constant_trip_count(ptr nocapture readonly %0) #0 { ; CHECK: Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(argmem: read) uwtable ; CHECK-LABEL: define {{[^@]+}}@loop_constant_trip_count -; CHECK-SAME: (ptr nocapture nofree nonnull readonly dereferenceable(4) [[TMP0:%.*]]) #[[ATTR13:[0-9]+]] { +; CHECK-SAME: (ptr nocapture nofree nonnull readonly dereferenceable(4) [[TMP0:%.*]]) #[[ATTR14:[0-9]+]] { ; CHECK-NEXT: br label [[TMP3:%.*]] ; CHECK: 2: ; CHECK-NEXT: ret i32 [[TMP8:%.*]] @@ -466,7 +491,7 @@ define i32 @loop_trip_count_unbound(i32 %0, i32 %1, ptr nocapture readonly %2, i32 %3) local_unnamed_addr #0 { ; CHECK: Function Attrs: nofree noinline norecurse nosync nounwind memory(argmem: read) uwtable ; CHECK-LABEL: define {{[^@]+}}@loop_trip_count_unbound -; CHECK-SAME: (i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], ptr nocapture nofree readonly [[TMP2:%.*]], i32 [[TMP3:%.*]]) local_unnamed_addr #[[ATTR14:[0-9]+]] { +; CHECK-SAME: (i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], ptr nocapture nofree readonly [[TMP2:%.*]], i32 [[TMP3:%.*]]) local_unnamed_addr #[[ATTR15:[0-9]+]] { ; CHECK-NEXT: [[TMP5:%.*]] = icmp eq i32 [[TMP0]], [[TMP1]] ; CHECK-NEXT: br i1 [[TMP5]], label [[TMP6:%.*]], label [[TMP8:%.*]] ; CHECK: 6: @@ -517,7 +542,7 @@ define i32 @loop_trip_dec(i32 %0, ptr nocapture readonly %1) local_unnamed_addr #0 { ; CHECK: Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(argmem: read) uwtable ; CHECK-LABEL: define {{[^@]+}}@loop_trip_dec -; CHECK-SAME: (i32 [[TMP0:%.*]], ptr nocapture nofree readonly [[TMP1:%.*]]) local_unnamed_addr #[[ATTR13]] { +; CHECK-SAME: (i32 [[TMP0:%.*]], ptr nocapture nofree readonly [[TMP1:%.*]]) local_unnamed_addr #[[ATTR14]] { ; CHECK-NEXT: [[TMP3:%.*]] = icmp sgt i32 [[TMP0]], -1 ; CHECK-NEXT: br i1 [[TMP3]], label [[TMP4:%.*]], label [[TMP14:%.*]] ; CHECK: 4: @@ -589,7 +614,7 @@ ; CHECK: Function Attrs: mustprogress noinline nounwind willreturn uwtable ; CHECK-LABEL: define {{[^@]+}}@unreachable_exit_positive1 ; CHECK-SAME: () #[[ATTR10]] { -; CHECK-NEXT: tail call void @will_return() #[[ATTR32]] +; CHECK-NEXT: tail call void @will_return() #[[ATTR33]] ; CHECK-NEXT: ret void ; CHECK: unreachable_label: ; CHECK-NEXT: unreachable @@ -664,7 +689,7 @@ define void @unreachable_exit_negative2() #0 { ; CHECK: Function Attrs: nofree noinline norecurse noreturn nosync nounwind memory(none) uwtable ; CHECK-LABEL: define {{[^@]+}}@unreachable_exit_negative2 -; CHECK-SAME: () #[[ATTR15:[0-9]+]] { +; CHECK-SAME: () #[[ATTR16:[0-9]+]] { ; CHECK-NEXT: br label [[L1:%.*]] ; CHECK: L1: ; CHECK-NEXT: br label [[L2:%.*]] @@ -713,7 +738,7 @@ define i32 @infinite_loop_inside_bounded_loop(i32 %n) { ; CHECK: Function Attrs: nofree norecurse nosync nounwind memory(none) ; CHECK-LABEL: define {{[^@]+}}@infinite_loop_inside_bounded_loop -; CHECK-SAME: (i32 [[N:%.*]]) #[[ATTR17:[0-9]+]] { +; CHECK-SAME: (i32 [[N:%.*]]) #[[ATTR18:[0-9]+]] { ; CHECK-NEXT: entry: ; CHECK-NEXT: br label [[FOR_COND:%.*]] ; CHECK: for.cond: @@ -774,7 +799,7 @@ define i32 @bounded_nested_loops(i32 %n) { ; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) ; CHECK-LABEL: define {{[^@]+}}@bounded_nested_loops -; CHECK-SAME: (i32 [[N:%.*]]) #[[ATTR18:[0-9]+]] { +; CHECK-SAME: (i32 [[N:%.*]]) #[[ATTR19:[0-9]+]] { ; CHECK-NEXT: entry: ; CHECK-NEXT: br label [[FOR_COND:%.*]] ; CHECK: for.cond: @@ -851,11 +876,11 @@ define i32 @bounded_loop_inside_unbounded_loop(i32 %n) { ; CHECK: Function Attrs: nofree norecurse nosync nounwind memory(none) ; CHECK-LABEL: define {{[^@]+}}@bounded_loop_inside_unbounded_loop -; CHECK-SAME: (i32 [[N:%.*]]) #[[ATTR17]] { +; CHECK-SAME: (i32 [[N:%.*]]) #[[ATTR18]] { ; CHECK-NEXT: entry: ; CHECK-NEXT: br label [[WHILE_COND:%.*]] ; CHECK: while.cond: -; CHECK-NEXT: [[ANS_0:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[TRUETMP2:%.*]], [[FOR_END:%.*]] ] +; CHECK-NEXT: [[ANS_0:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[TMP2:%.*]], [[FOR_END:%.*]] ] ; CHECK-NEXT: [[N_ADDR_0:%.*]] = phi i32 [ [[N]], [[ENTRY]] ], [ [[INC:%.*]], [[FOR_END]] ] ; CHECK-NEXT: [[TMP:%.*]] = icmp sgt i32 [[N_ADDR_0]], -1 ; CHECK-NEXT: [[SMAX:%.*]] = select i1 [[TMP]], i32 [[N_ADDR_0]], i32 -1 @@ -863,12 +888,12 @@ ; CHECK-NEXT: [[TOBOOL:%.*]] = icmp eq i32 [[N_ADDR_0]], 0 ; CHECK-NEXT: br i1 [[TOBOOL]], label [[WHILE_END:%.*]], label [[WHILE_BODY:%.*]] ; CHECK: while.body: -; CHECK-NEXT: [[TRUETMP1:%.*]] = add i32 [[ANS_0]], 1 +; CHECK-NEXT: [[TMP1:%.*]] = add i32 [[ANS_0]], 1 ; CHECK-NEXT: br label [[FOR_COND:%.*]] ; CHECK: for.cond: ; CHECK-NEXT: br i1 true, label [[FOR_COND_CLEANUP:%.*]], label [[FOR_BODY:%.*]] ; CHECK: for.cond.cleanup: -; CHECK-NEXT: [[TRUETMP2]] = add i32 [[TRUETMP1]], [[SMAX]] +; CHECK-NEXT: [[TMP2]] = add i32 [[TMP1]], [[SMAX]] ; CHECK-NEXT: br label [[FOR_END]] ; CHECK: for.body: ; CHECK-NEXT: unreachable @@ -935,11 +960,11 @@ define i32 @nested_unbounded_loops(i32 %n) { ; CHECK: Function Attrs: nofree norecurse nosync nounwind memory(none) ; CHECK-LABEL: define {{[^@]+}}@nested_unbounded_loops -; CHECK-SAME: (i32 [[N:%.*]]) #[[ATTR17]] { +; CHECK-SAME: (i32 [[N:%.*]]) #[[ATTR18]] { ; CHECK-NEXT: entry: ; CHECK-NEXT: br label [[WHILE_COND:%.*]] ; CHECK: while.cond: -; CHECK-NEXT: [[ANS_0:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[TRUETMP1:%.*]], [[WHILE_END10:%.*]] ] +; CHECK-NEXT: [[ANS_0:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[TMP1:%.*]], [[WHILE_END10:%.*]] ] ; CHECK-NEXT: [[N_ADDR_0:%.*]] = phi i32 [ [[N]], [[ENTRY]] ], [ -1, [[WHILE_END10]] ] ; CHECK-NEXT: [[TOBOOL:%.*]] = icmp eq i32 [[N_ADDR_0]], 0 ; CHECK-NEXT: br i1 [[TOBOOL]], label [[WHILE_END11:%.*]], label [[WHILE_BODY:%.*]] @@ -957,7 +982,7 @@ ; CHECK: while.body8: ; CHECK-NEXT: unreachable ; CHECK: while.end10: -; CHECK-NEXT: [[TRUETMP1]] = add i32 [[TMP]], [[ANS_0]] +; CHECK-NEXT: [[TMP1]] = add i32 [[TMP]], [[ANS_0]] ; CHECK-NEXT: br label [[WHILE_COND]] ; CHECK: while.end11: ; CHECK-NEXT: [[ANS_0_LCSSA:%.*]] = phi i32 [ [[ANS_0]], [[WHILE_COND]] ] @@ -1025,9 +1050,9 @@ define void @non_loop_cycle(i32 %n) { ; TUNIT: Function Attrs: nofree norecurse nosync nounwind memory(none) ; TUNIT-LABEL: define {{[^@]+}}@non_loop_cycle -; TUNIT-SAME: (i32 [[N:%.*]]) #[[ATTR17]] { +; TUNIT-SAME: (i32 [[N:%.*]]) #[[ATTR18]] { ; TUNIT-NEXT: entry: -; TUNIT-NEXT: [[CALL:%.*]] = call i32 @fact_loop(i32 [[N]]) #[[ATTR27]] +; TUNIT-NEXT: [[CALL:%.*]] = call i32 @fact_loop(i32 [[N]]) #[[ATTR28]] ; TUNIT-NEXT: [[CMP:%.*]] = icmp sgt i32 [[CALL]], 5 ; TUNIT-NEXT: br i1 [[CMP]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]] ; TUNIT: if.then: @@ -1035,7 +1060,7 @@ ; TUNIT: if.else: ; TUNIT-NEXT: br label [[ENTRY2:%.*]] ; TUNIT: entry1: -; TUNIT-NEXT: [[CALL1:%.*]] = call i32 @fact_loop(i32 [[N]]) #[[ATTR27]] +; TUNIT-NEXT: [[CALL1:%.*]] = call i32 @fact_loop(i32 [[N]]) #[[ATTR28]] ; TUNIT-NEXT: [[CMP2:%.*]] = icmp sgt i32 [[CALL1]], 5 ; TUNIT-NEXT: br i1 [[CMP2]], label [[IF_THEN3:%.*]], label [[IF_ELSE4:%.*]] ; TUNIT: if.then3: @@ -1043,7 +1068,7 @@ ; TUNIT: if.else4: ; TUNIT-NEXT: br label [[ENTRY2]] ; TUNIT: entry2: -; TUNIT-NEXT: [[CALL5:%.*]] = call i32 @fact_loop(i32 [[N]]) #[[ATTR27]] +; TUNIT-NEXT: [[CALL5:%.*]] = call i32 @fact_loop(i32 [[N]]) #[[ATTR28]] ; TUNIT-NEXT: [[CMP6:%.*]] = icmp sgt i32 [[CALL5]], 5 ; TUNIT-NEXT: br i1 [[CMP6]], label [[IF_THEN7:%.*]], label [[IF_ELSE8:%.*]] ; TUNIT: if.then7: @@ -1055,9 +1080,9 @@ ; ; CGSCC: Function Attrs: nofree nosync nounwind memory(none) ; CGSCC-LABEL: define {{[^@]+}}@non_loop_cycle -; CGSCC-SAME: (i32 [[N:%.*]]) #[[ATTR19]] { +; CGSCC-SAME: (i32 [[N:%.*]]) #[[ATTR20]] { ; CGSCC-NEXT: entry: -; CGSCC-NEXT: [[CALL:%.*]] = call i32 @fact_loop(i32 [[N]]) #[[ATTR34:[0-9]+]] +; CGSCC-NEXT: [[CALL:%.*]] = call i32 @fact_loop(i32 [[N]]) #[[ATTR35:[0-9]+]] ; CGSCC-NEXT: [[CMP:%.*]] = icmp sgt i32 [[CALL]], 5 ; CGSCC-NEXT: br i1 [[CMP]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]] ; CGSCC: if.then: @@ -1065,7 +1090,7 @@ ; CGSCC: if.else: ; CGSCC-NEXT: br label [[ENTRY2:%.*]] ; CGSCC: entry1: -; CGSCC-NEXT: [[CALL1:%.*]] = call i32 @fact_loop(i32 [[N]]) #[[ATTR34]] +; CGSCC-NEXT: [[CALL1:%.*]] = call i32 @fact_loop(i32 [[N]]) #[[ATTR35]] ; CGSCC-NEXT: [[CMP2:%.*]] = icmp sgt i32 [[CALL1]], 5 ; CGSCC-NEXT: br i1 [[CMP2]], label [[IF_THEN3:%.*]], label [[IF_ELSE4:%.*]] ; CGSCC: if.then3: @@ -1073,7 +1098,7 @@ ; CGSCC: if.else4: ; CGSCC-NEXT: br label [[ENTRY2]] ; CGSCC: entry2: -; CGSCC-NEXT: [[CALL5:%.*]] = call i32 @fact_loop(i32 [[N]]) #[[ATTR34]] +; CGSCC-NEXT: [[CALL5:%.*]] = call i32 @fact_loop(i32 [[N]]) #[[ATTR35]] ; CGSCC-NEXT: [[CMP6:%.*]] = icmp sgt i32 [[CALL5]], 5 ; CGSCC-NEXT: br i1 [[CMP6]], label [[IF_THEN7:%.*]], label [[IF_ELSE8:%.*]] ; CGSCC: if.then7: @@ -1129,13 +1154,13 @@ define void @willreturn_mustprogress_caller_1() mustprogress { ; TUNIT: Function Attrs: mustprogress ; TUNIT-LABEL: define {{[^@]+}}@willreturn_mustprogress_caller_1 -; TUNIT-SAME: () #[[ATTR21:[0-9]+]] { +; TUNIT-SAME: () #[[ATTR22:[0-9]+]] { ; TUNIT-NEXT: call void @unknown() ; TUNIT-NEXT: ret void ; ; CGSCC: Function Attrs: mustprogress ; CGSCC-LABEL: define {{[^@]+}}@willreturn_mustprogress_caller_1 -; CGSCC-SAME: () #[[ATTR22:[0-9]+]] { +; CGSCC-SAME: () #[[ATTR23:[0-9]+]] { ; CGSCC-NEXT: call void @unknown() ; CGSCC-NEXT: ret void ; @@ -1145,14 +1170,14 @@ define void @willreturn_mustprogress_caller_2() mustprogress { ; TUNIT: Function Attrs: mustprogress nosync willreturn memory(read) ; TUNIT-LABEL: define {{[^@]+}}@willreturn_mustprogress_caller_2 -; TUNIT-SAME: () #[[ATTR23:[0-9]+]] { -; TUNIT-NEXT: call void @readonly() #[[ATTR34:[0-9]+]] +; TUNIT-SAME: () #[[ATTR24:[0-9]+]] { +; TUNIT-NEXT: call void @readonly() #[[ATTR35:[0-9]+]] ; TUNIT-NEXT: ret void ; ; CGSCC: Function Attrs: mustprogress nosync willreturn memory(read) ; CGSCC-LABEL: define {{[^@]+}}@willreturn_mustprogress_caller_2 -; CGSCC-SAME: () #[[ATTR24:[0-9]+]] { -; CGSCC-NEXT: call void @readonly() #[[ATTR35:[0-9]+]] +; CGSCC-SAME: () #[[ATTR25:[0-9]+]] { +; CGSCC-NEXT: call void @readonly() #[[ATTR36:[0-9]+]] ; CGSCC-NEXT: ret void ; call void @readonly() @@ -1161,14 +1186,14 @@ define void @willreturn_mustprogress_caller_3() mustprogress { ; TUNIT: Function Attrs: mustprogress nosync willreturn memory(none) ; TUNIT-LABEL: define {{[^@]+}}@willreturn_mustprogress_caller_3 -; TUNIT-SAME: () #[[ATTR24:[0-9]+]] { -; TUNIT-NEXT: call void @readnone() #[[ATTR35:[0-9]+]] +; TUNIT-SAME: () #[[ATTR25:[0-9]+]] { +; TUNIT-NEXT: call void @readnone() #[[ATTR36:[0-9]+]] ; TUNIT-NEXT: ret void ; ; CGSCC: Function Attrs: mustprogress nosync willreturn memory(none) ; CGSCC-LABEL: define {{[^@]+}}@willreturn_mustprogress_caller_3 -; CGSCC-SAME: () #[[ATTR25:[0-9]+]] { -; CGSCC-NEXT: call void @readnone() #[[ATTR36:[0-9]+]] +; CGSCC-SAME: () #[[ATTR26:[0-9]+]] { +; CGSCC-NEXT: call void @readnone() #[[ATTR37:[0-9]+]] ; CGSCC-NEXT: ret void ; call void @readnone() @@ -1185,14 +1210,14 @@ define void @willreturn_mustprogress_callee_2() { ; TUNIT: Function Attrs: mustprogress nosync willreturn memory(read) ; TUNIT-LABEL: define {{[^@]+}}@willreturn_mustprogress_callee_2 -; TUNIT-SAME: () #[[ATTR23]] { -; TUNIT-NEXT: call void @readonly_mustprogress() #[[ATTR36:[0-9]+]] +; TUNIT-SAME: () #[[ATTR24]] { +; TUNIT-NEXT: call void @readonly_mustprogress() #[[ATTR37:[0-9]+]] ; TUNIT-NEXT: ret void ; ; CGSCC: Function Attrs: mustprogress nosync willreturn memory(read) ; CGSCC-LABEL: define {{[^@]+}}@willreturn_mustprogress_callee_2 -; CGSCC-SAME: () #[[ATTR24]] { -; CGSCC-NEXT: call void @readonly_mustprogress() #[[ATTR37:[0-9]+]] +; CGSCC-SAME: () #[[ATTR25]] { +; CGSCC-NEXT: call void @readonly_mustprogress() #[[ATTR38:[0-9]+]] ; CGSCC-NEXT: ret void ; call void @readonly_mustprogress() @@ -1209,14 +1234,14 @@ define void @willreturn_mustprogress_callee_4() { ; TUNIT: Function Attrs: mustprogress nosync willreturn memory(read) ; TUNIT-LABEL: define {{[^@]+}}@willreturn_mustprogress_callee_4 -; TUNIT-SAME: () #[[ATTR23]] { -; TUNIT-NEXT: call void @willreturn_mustprogress_callee_2() #[[ATTR36]] +; TUNIT-SAME: () #[[ATTR24]] { +; TUNIT-NEXT: call void @willreturn_mustprogress_callee_2() #[[ATTR37]] ; TUNIT-NEXT: ret void ; ; CGSCC: Function Attrs: mustprogress nosync willreturn memory(read) ; CGSCC-LABEL: define {{[^@]+}}@willreturn_mustprogress_callee_4 -; CGSCC-SAME: () #[[ATTR24]] { -; CGSCC-NEXT: call void @willreturn_mustprogress_callee_2() #[[ATTR38:[0-9]+]] +; CGSCC-SAME: () #[[ATTR25]] { +; CGSCC-NEXT: call void @willreturn_mustprogress_callee_2() #[[ATTR39:[0-9]+]] ; CGSCC-NEXT: ret void ; call void @willreturn_mustprogress_callee_2() @@ -1226,12 +1251,12 @@ define weak void @implied_mustprogress1() willreturn { ; TUNIT: Function Attrs: mustprogress willreturn ; TUNIT-LABEL: define {{[^@]+}}@implied_mustprogress1 -; TUNIT-SAME: () #[[ATTR25:[0-9]+]] { +; TUNIT-SAME: () #[[ATTR26:[0-9]+]] { ; TUNIT-NEXT: ret void ; ; CGSCC: Function Attrs: mustprogress willreturn ; CGSCC-LABEL: define {{[^@]+}}@implied_mustprogress1 -; CGSCC-SAME: () #[[ATTR26:[0-9]+]] { +; CGSCC-SAME: () #[[ATTR27:[0-9]+]] { ; CGSCC-NEXT: ret void ; ret void @@ -1239,12 +1264,12 @@ define weak void @implied_willreturn1() readnone mustprogress { ; TUNIT: Function Attrs: mustprogress nosync willreturn memory(none) ; TUNIT-LABEL: define {{[^@]+}}@implied_willreturn1 -; TUNIT-SAME: () #[[ATTR24]] { +; TUNIT-SAME: () #[[ATTR25]] { ; TUNIT-NEXT: ret void ; ; CGSCC: Function Attrs: mustprogress nosync willreturn memory(none) ; CGSCC-LABEL: define {{[^@]+}}@implied_willreturn1 -; CGSCC-SAME: () #[[ATTR25]] { +; CGSCC-SAME: () #[[ATTR26]] { ; CGSCC-NEXT: ret void ; ret void @@ -1252,12 +1277,12 @@ define weak void @implied_willreturn2() readonly mustprogress { ; TUNIT: Function Attrs: mustprogress nosync willreturn memory(read) ; TUNIT-LABEL: define {{[^@]+}}@implied_willreturn2 -; TUNIT-SAME: () #[[ATTR23]] { +; TUNIT-SAME: () #[[ATTR24]] { ; TUNIT-NEXT: ret void ; ; CGSCC: Function Attrs: mustprogress nosync willreturn memory(read) ; CGSCC-LABEL: define {{[^@]+}}@implied_willreturn2 -; CGSCC-SAME: () #[[ATTR24]] { +; CGSCC-SAME: () #[[ATTR25]] { ; CGSCC-NEXT: ret void ; ret void @@ -1265,12 +1290,12 @@ define weak void @not_implied_willreturn1() mustprogress { ; TUNIT: Function Attrs: mustprogress ; TUNIT-LABEL: define {{[^@]+}}@not_implied_willreturn1 -; TUNIT-SAME: () #[[ATTR21]] { +; TUNIT-SAME: () #[[ATTR22]] { ; TUNIT-NEXT: ret void ; ; CGSCC: Function Attrs: mustprogress ; CGSCC-LABEL: define {{[^@]+}}@not_implied_willreturn1 -; CGSCC-SAME: () #[[ATTR22]] { +; CGSCC-SAME: () #[[ATTR23]] { ; CGSCC-NEXT: ret void ; ret void @@ -1278,12 +1303,12 @@ define weak void @not_implied_willreturn2() readnone { ; TUNIT: Function Attrs: nosync memory(none) ; TUNIT-LABEL: define {{[^@]+}}@not_implied_willreturn2 -; TUNIT-SAME: () #[[ATTR26:[0-9]+]] { +; TUNIT-SAME: () #[[ATTR27:[0-9]+]] { ; TUNIT-NEXT: ret void ; ; CGSCC: Function Attrs: nosync memory(none) ; CGSCC-LABEL: define {{[^@]+}}@not_implied_willreturn2 -; CGSCC-SAME: () #[[ATTR27:[0-9]+]] { +; CGSCC-SAME: () #[[ATTR28:[0-9]+]] { ; CGSCC-NEXT: ret void ; ret void @@ -1305,30 +1330,31 @@ ; TUNIT: attributes #[[ATTR10]] = { mustprogress noinline nounwind willreturn uwtable } ; TUNIT: attributes #[[ATTR11:[0-9]+]] = { noinline willreturn uwtable } ; TUNIT: attributes #[[ATTR12]] = { mustprogress nounwind willreturn } -; TUNIT: attributes #[[ATTR13]] = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(argmem: read) uwtable } -; TUNIT: attributes #[[ATTR14]] = { nofree noinline norecurse nosync nounwind memory(argmem: read) uwtable } -; TUNIT: attributes #[[ATTR15]] = { nofree noinline norecurse noreturn nosync nounwind memory(none) uwtable } -; TUNIT: attributes #[[ATTR16:[0-9]+]] = { noreturn nounwind } -; TUNIT: attributes #[[ATTR17]] = { nofree norecurse nosync nounwind memory(none) } -; TUNIT: attributes #[[ATTR18]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) } -; TUNIT: attributes #[[ATTR19:[0-9]+]] = { memory(read) } -; TUNIT: attributes #[[ATTR20:[0-9]+]] = { memory(none) } -; TUNIT: attributes #[[ATTR21]] = { mustprogress } -; TUNIT: attributes #[[ATTR22:[0-9]+]] = { mustprogress memory(read) } -; TUNIT: attributes #[[ATTR23]] = { mustprogress nosync willreturn memory(read) } -; TUNIT: attributes #[[ATTR24]] = { mustprogress nosync willreturn memory(none) } -; TUNIT: attributes #[[ATTR25]] = { mustprogress willreturn } -; TUNIT: attributes #[[ATTR26]] = { nosync memory(none) } -; TUNIT: attributes #[[ATTR27]] = { nofree nosync nounwind memory(none) } -; TUNIT: attributes #[[ATTR28]] = { nofree nounwind willreturn } -; TUNIT: attributes #[[ATTR29]] = { nofree nosync nounwind } -; TUNIT: attributes #[[ATTR30]] = { nofree nosync willreturn } -; TUNIT: attributes #[[ATTR31]] = { nounwind } -; TUNIT: attributes #[[ATTR32]] = { willreturn } -; TUNIT: attributes #[[ATTR33]] = { nounwind willreturn } -; TUNIT: attributes #[[ATTR34]] = { nosync memory(read) } -; TUNIT: attributes #[[ATTR35]] = { nosync } -; TUNIT: attributes #[[ATTR36]] = { nosync willreturn memory(read) } +; TUNIT: attributes #[[ATTR13]] = { mustprogress noinline willreturn uwtable } +; TUNIT: attributes #[[ATTR14]] = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(argmem: read) uwtable } +; TUNIT: attributes #[[ATTR15]] = { nofree noinline norecurse nosync nounwind memory(argmem: read) uwtable } +; TUNIT: attributes #[[ATTR16]] = { nofree noinline norecurse noreturn nosync nounwind memory(none) uwtable } +; TUNIT: attributes #[[ATTR17:[0-9]+]] = { noreturn nounwind } +; TUNIT: attributes #[[ATTR18]] = { nofree norecurse nosync nounwind memory(none) } +; TUNIT: attributes #[[ATTR19]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) } +; TUNIT: attributes #[[ATTR20:[0-9]+]] = { memory(read) } +; TUNIT: attributes #[[ATTR21:[0-9]+]] = { memory(none) } +; TUNIT: attributes #[[ATTR22]] = { mustprogress } +; TUNIT: attributes #[[ATTR23:[0-9]+]] = { mustprogress memory(read) } +; TUNIT: attributes #[[ATTR24]] = { mustprogress nosync willreturn memory(read) } +; TUNIT: attributes #[[ATTR25]] = { mustprogress nosync willreturn memory(none) } +; TUNIT: attributes #[[ATTR26]] = { mustprogress willreturn } +; TUNIT: attributes #[[ATTR27]] = { nosync memory(none) } +; TUNIT: attributes #[[ATTR28]] = { nofree nosync nounwind memory(none) } +; TUNIT: attributes #[[ATTR29]] = { nofree nounwind willreturn } +; TUNIT: attributes #[[ATTR30]] = { nofree nosync nounwind } +; TUNIT: attributes #[[ATTR31]] = { nofree nosync willreturn } +; TUNIT: attributes #[[ATTR32]] = { nounwind } +; TUNIT: attributes #[[ATTR33]] = { willreturn } +; TUNIT: attributes #[[ATTR34]] = { nounwind willreturn } +; TUNIT: attributes #[[ATTR35]] = { nosync memory(read) } +; TUNIT: attributes #[[ATTR36]] = { nosync } +; TUNIT: attributes #[[ATTR37]] = { nosync willreturn memory(read) } ;. ; CGSCC: attributes #[[ATTR0]] = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(none) uwtable } ; CGSCC: attributes #[[ATTR1]] = { nofree noinline nosync nounwind memory(none) uwtable } @@ -1343,30 +1369,31 @@ ; CGSCC: attributes #[[ATTR10]] = { mustprogress noinline nounwind willreturn uwtable } ; CGSCC: attributes #[[ATTR11:[0-9]+]] = { noinline willreturn uwtable } ; CGSCC: attributes #[[ATTR12]] = { mustprogress nounwind willreturn } -; CGSCC: attributes #[[ATTR13]] = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(argmem: read) uwtable } -; CGSCC: attributes #[[ATTR14]] = { nofree noinline norecurse nosync nounwind memory(argmem: read) uwtable } -; CGSCC: attributes #[[ATTR15]] = { nofree noinline norecurse noreturn nosync nounwind memory(none) uwtable } -; CGSCC: attributes #[[ATTR16:[0-9]+]] = { noreturn nounwind } -; CGSCC: attributes #[[ATTR17]] = { nofree norecurse nosync nounwind memory(none) } -; CGSCC: attributes #[[ATTR18]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) } -; CGSCC: attributes #[[ATTR19]] = { nofree nosync nounwind memory(none) } -; CGSCC: attributes #[[ATTR20:[0-9]+]] = { memory(read) } -; CGSCC: attributes #[[ATTR21:[0-9]+]] = { memory(none) } -; CGSCC: attributes #[[ATTR22]] = { mustprogress } -; CGSCC: attributes #[[ATTR23:[0-9]+]] = { mustprogress memory(read) } -; CGSCC: attributes #[[ATTR24]] = { mustprogress nosync willreturn memory(read) } -; CGSCC: attributes #[[ATTR25]] = { mustprogress nosync willreturn memory(none) } -; CGSCC: attributes #[[ATTR26]] = { mustprogress willreturn } -; CGSCC: attributes #[[ATTR27]] = { nosync memory(none) } -; CGSCC: attributes #[[ATTR28]] = { nofree nounwind willreturn } -; CGSCC: attributes #[[ATTR29]] = { nofree nosync nounwind } -; CGSCC: attributes #[[ATTR30]] = { nofree nosync willreturn } -; CGSCC: attributes #[[ATTR31]] = { nounwind } -; CGSCC: attributes #[[ATTR32]] = { willreturn } -; CGSCC: attributes #[[ATTR33]] = { nounwind willreturn } -; CGSCC: attributes #[[ATTR34]] = { nofree nosync } -; CGSCC: attributes #[[ATTR35]] = { nosync memory(read) } -; CGSCC: attributes #[[ATTR36]] = { nosync } -; CGSCC: attributes #[[ATTR37]] = { nosync willreturn memory(read) } -; CGSCC: attributes #[[ATTR38]] = { willreturn memory(read) } +; CGSCC: attributes #[[ATTR13]] = { mustprogress noinline willreturn uwtable } +; CGSCC: attributes #[[ATTR14]] = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(argmem: read) uwtable } +; CGSCC: attributes #[[ATTR15]] = { nofree noinline norecurse nosync nounwind memory(argmem: read) uwtable } +; CGSCC: attributes #[[ATTR16]] = { nofree noinline norecurse noreturn nosync nounwind memory(none) uwtable } +; CGSCC: attributes #[[ATTR17:[0-9]+]] = { noreturn nounwind } +; CGSCC: attributes #[[ATTR18]] = { nofree norecurse nosync nounwind memory(none) } +; CGSCC: attributes #[[ATTR19]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) } +; CGSCC: attributes #[[ATTR20]] = { nofree nosync nounwind memory(none) } +; CGSCC: attributes #[[ATTR21:[0-9]+]] = { memory(read) } +; CGSCC: attributes #[[ATTR22:[0-9]+]] = { memory(none) } +; CGSCC: attributes #[[ATTR23]] = { mustprogress } +; CGSCC: attributes #[[ATTR24:[0-9]+]] = { mustprogress memory(read) } +; CGSCC: attributes #[[ATTR25]] = { mustprogress nosync willreturn memory(read) } +; CGSCC: attributes #[[ATTR26]] = { mustprogress nosync willreturn memory(none) } +; CGSCC: attributes #[[ATTR27]] = { mustprogress willreturn } +; CGSCC: attributes #[[ATTR28]] = { nosync memory(none) } +; CGSCC: attributes #[[ATTR29]] = { nofree nounwind willreturn } +; CGSCC: attributes #[[ATTR30]] = { nofree nosync nounwind } +; CGSCC: attributes #[[ATTR31]] = { nofree nosync willreturn } +; CGSCC: attributes #[[ATTR32]] = { nounwind } +; CGSCC: attributes #[[ATTR33]] = { willreturn } +; CGSCC: attributes #[[ATTR34]] = { nounwind willreturn } +; CGSCC: attributes #[[ATTR35]] = { nofree nosync } +; CGSCC: attributes #[[ATTR36]] = { nosync memory(read) } +; CGSCC: attributes #[[ATTR37]] = { nosync } +; CGSCC: attributes #[[ATTR38]] = { nosync willreturn memory(read) } +; CGSCC: attributes #[[ATTR39]] = { willreturn memory(read) } ;. 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); @@ -129,6 +134,8 @@ declare void @abort() nounwind @catch_ty = external global ptr +; Do not deduce nounwind for a invoke which doesn't catch everything, +; as some exception types will pass through. define void @catch_specific_landingpad() personality ptr @__gxx_personality_v0 { ; CHECK: Function Attrs: noreturn ; CHECK-LABEL: define {{[^@]+}}@catch_specific_landingpad @@ -156,6 +163,7 @@ unreachable } +; We _should_ deduce nounwind for a catch-all landingpad. define void @catch_all_landingpad() personality ptr @__gxx_personality_v0 { ; CHECK: Function Attrs: noreturn nounwind ; CHECK-LABEL: define {{[^@]+}}@catch_all_landingpad @@ -183,6 +191,7 @@ unreachable } +; Do not deduce nounwind for a filter-some landingpad. define void @filter_specific_landingpad() personality ptr @__gxx_personality_v0 { ; CHECK: Function Attrs: noreturn ; CHECK-LABEL: define {{[^@]+}}@filter_specific_landingpad @@ -210,6 +219,7 @@ unreachable } +; We _should_ deduce nounwind for a filter-none landingpad. define void @filter_none_landingpad() personality ptr @__gxx_personality_v0 { ; CHECK: Function Attrs: noreturn nounwind ; CHECK-LABEL: define {{[^@]+}}@filter_none_landingpad @@ -237,6 +247,12 @@ unreachable } +; Do not deduce nounwind based on a cleanup clause. Even though +; unwinding through this function cannot actually reach the parent, +; since it never calls 'resume', the Phase-1 unwind process looks past +; cleanup handlers to search for the final catch handler. It breaks +; program semantics to cause that catch-handler to be unfindable, so +; we must not infer nounwind. define void @cleanup_landingpad() personality ptr @__gxx_personality_v0 { ; CHECK: Function Attrs: noreturn ; CHECK-LABEL: define {{[^@]+}}@cleanup_landingpad @@ -264,6 +280,9 @@ unreachable } +; This test is similar to cleanup_landingpad test case, but with +; win32-style IR. For similar reasons, we don't mark this nounwind, +; despite the lack of ability to unwind to the parent. define void @cleanuppad() personality ptr @__gxx_personality_v0 { ; CHECK: Function Attrs: noreturn ; CHECK-LABEL: define {{[^@]+}}@cleanuppad @@ -289,6 +308,7 @@ unreachable } +; And, same where some types are caught and others handled as cleanup. define void @catchswitch_cleanuppad() personality ptr @__gxx_personality_v0 { ; CHECK: Function Attrs: noreturn ; CHECK-LABEL: define {{[^@]+}}@catchswitch_cleanuppad @@ -328,6 +348,19 @@ unreachable } +; 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:%.*]]) #[[ATTR2:[0-9]+]] 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 i32 @__gxx_personality_v0(...) declare ptr @__cxa_begin_catch(ptr) 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}