diff --git a/llvm/include/llvm/IR/InstrTypes.h b/llvm/include/llvm/IR/InstrTypes.h --- a/llvm/include/llvm/IR/InstrTypes.h +++ b/llvm/include/llvm/IR/InstrTypes.h @@ -1768,6 +1768,9 @@ /// Returns true if this function is guaranteed to return. bool willReturn() const { return hasFnAttr(Attribute::WillReturn); } + void setWillReturn() { + addAttribute(AttributeList::FunctionIndex, Attribute::WillReturn); + } void setOnlyReadsMemory() { addAttribute(AttributeList::FunctionIndex, Attribute::ReadOnly); 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 @@ -1433,7 +1433,46 @@ return Changed; } -static bool functionWillReturn(const Function &F) { +namespace { +/// Helper to indicate whether all call sites in a function are willreturn, may +/// return or that status is unknown because no analysis was done. +enum class CallsiteStatusTy { AllWillReturn, MayNotReturn, Unknown }; +} // namespace + +// Add willreturn to callsites in F, if possible. +static std::pair callsitesWillReturn(Function &F) { + // Can only analyze functions with a definition. + if (F.isDeclaration()) + return {false, CallsiteStatus::Unknown}; + + bool FnWillReturn = F.willReturn(); + // Unless F must make progress or willreturn, we cannot add willreturn at + // callsites in the function. + if (!F.mustProgress() && !FnWillReturn) + return {false, CallsiteStatus::Unknown}; + + bool Changed = false; + CallsiteStatus AllCallsWillReturn = CallsiteStatus::AllWillReturn; + // Add willreturn to all callsites that only read memory or if the function + // itself is willreturn. + for (Instruction &I : instructions(F)) { + auto *CB = dyn_cast(&I); + if (!CB) + continue; + + if (FnWillReturn || CB->onlyReadsMemory()) { + if (CB->willReturn()) + continue; + CB->setWillReturn(); + Changed = true; + } else { + AllCallsWillReturn = CallsiteStatus::MayNotReturn; + } + } + return {Changed, AllCallsWillReturn}; +} + +static bool functionWillReturn(const Function &F, CallsiteStatus CS) { // Must-progress function without side-effects must return. if (F.mustProgress() && F.onlyReadsMemory()) return true; @@ -1451,10 +1490,12 @@ // If there are no loops, then the function is willreturn if all calls in // it are willreturn. - return all_of(instructions(F), [](const Instruction &I) { - const auto *CB = dyn_cast(&I); - return !CB || CB->hasFnAttr(Attribute::WillReturn); - }); + if (CS == CallsiteStatus::Unknown) + return all_of(instructions(F), [](const Instruction &I) { + const auto *CB = dyn_cast(&I); + return !CB || CB->willReturn(); + }); + return CS == CallsiteStatus::AllWillReturn; } // Set the willreturn function attribute if possible. @@ -1462,9 +1503,15 @@ bool Changed = false; for (Function *F : SCCNodes) { - if (!F || F->willReturn() || !functionWillReturn(*F)) + if (!F) continue; + bool Modified; + CallsiteStatus CSStatus; + std::tie(Modified, CSStatus) = callsitesWillReturn(*F); + Changed |= Modified; + if (F->willReturn() || !functionWillReturn(*F, CSStatus)) + continue; F->setWillReturn(); NumWillReturn++; Changed = true; diff --git a/llvm/test/Transforms/FunctionAttrs/willreturn-callsites.ll b/llvm/test/Transforms/FunctionAttrs/willreturn-callsites.ll --- a/llvm/test/Transforms/FunctionAttrs/willreturn-callsites.ll +++ b/llvm/test/Transforms/FunctionAttrs/willreturn-callsites.ll @@ -8,8 +8,8 @@ define void @test_fn_mustprogress(i32* %ptr) mustprogress { ; CHECK: Function Attrs: mustprogress ; CHECK-LABEL: @test_fn_mustprogress( -; CHECK-NOT: call void @decl_readonly() # -; CHECK-NOT: call void @decl_readnone() # +; CHECK-NEXT: call void @decl_readonly() [[WILLRETURN:#.*]] +; CHECK-NEXT: call void @decl_readnone() [[WILLRETURN]] ; CHECK-NOT: call void @decl_unknown() # ; CHECK-NOT: call void @decl_argmemonly(i32* [[PTR:%.*]]) # ; CHECK: ret void @@ -24,11 +24,11 @@ define void @test_fn_willreturn(i32* %ptr) willreturn { ; CHECK: Function Attrs: willreturn ; CHECK-LABEL: @test_fn_willreturn( -; CHECK-NOT: call void @decl_readonly() # -; CHECK-NOT : call void @decl_readnone() # -; CHECK-NOT: call void @decl_unknown() # -; CHECK-NOT: call void @decl_argmemonly(i32* [[PTR:%.*]]) # -; CHECK: ret void +; CHECK-NEXT: call void @decl_readonly() [[WILLRETURN]] +; CHECK-NEXT: call void @decl_readnone() [[WILLRETURN]] +; CHECK-NEXT: call void @decl_unknown() [[WILLRETURN]] +; CHECK-NEXT: call void @decl_argmemonly(i32* [[PTR:%.*]]) [[WILLRETURN]] +; CHECK-NEXT: ret void ; call void @decl_readonly() call void @decl_readnone() @@ -40,9 +40,9 @@ define void @test_fn_mustprogress_readonly_calls(i32* %ptr) mustprogress { ; CHECK: Function Attrs: readonly willreturn mustprogress ; CHECK-LABEL: @test_fn_mustprogress_readonly_calls( -; CHECK-NOT: call void @decl_readonly() # -; CHECK-NOT: call void @decl_readnone() # -; CHECK: ret void +; CHECK-NEXT: call void @decl_readonly() [[WILLRETURN]] +; CHECK-NEXT: call void @decl_readnone() [[WILLRETURN]] +; CHECK-NEXT: ret void ; call void @decl_readonly() call void @decl_readnone() @@ -50,11 +50,11 @@ } define void @test_fn_mustprogress_readonly_calls_but_stores(i32* %ptr) mustprogress { -; CHECK: Function Attrs: nofree mustprogress +; CHECK: Function Attrs: nofree willreturn mustprogress ; CHECK-LABEL: @test_fn_mustprogress_readonly_calls_but_stores( -; CHECK-NOT: call void @decl_readonly() # -; CHECK-NOT: call void @decl_readnone() # -; CHECK: store i32 0, i32* [[PTR:%.*]], align 4 +; CHECK-NEXT: call void @decl_readonly() [[WILLRETURN]] +; CHECK-NEXT: call void @decl_readnone() [[WILLRETURN]] +; CHECK-NEXT: store i32 0, i32* [[PTR:%.*]], align 4 ; CHECK-NEXT: ret void ; call void @decl_readonly() @@ -62,3 +62,5 @@ store i32 0, i32* %ptr ret void } + +; CHECK: attributes [[WILLRETURN]] = { willreturn }