diff --git a/llvm/include/llvm/Transforms/IPO/FunctionAttrs.h b/llvm/include/llvm/Transforms/IPO/FunctionAttrs.h index 754714dceb7a6..bf2095bf7286a 100644 --- a/llvm/include/llvm/Transforms/IPO/FunctionAttrs.h +++ b/llvm/include/llvm/Transforms/IPO/FunctionAttrs.h @@ -49,8 +49,9 @@ LLVM_ABI bool thinLTOPropagateFunctionAttrs( /// attribute. It also discovers function arguments that are not captured by /// the function and marks them with the nocapture attribute. struct PostOrderFunctionAttrsPass : PassInfoMixin { - PostOrderFunctionAttrsPass(bool SkipNonRecursive = false) - : SkipNonRecursive(SkipNonRecursive) {} + PostOrderFunctionAttrsPass(bool SkipNonRecursive = false, + bool IsLTOPostLink = false) + : SkipNonRecursive(SkipNonRecursive), IsLTOPostLink(IsLTOPostLink) {} LLVM_ABI PreservedAnalyses run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR); @@ -61,6 +62,7 @@ struct PostOrderFunctionAttrsPass : PassInfoMixin { private: bool SkipNonRecursive; + bool IsLTOPostLink; }; /// A pass to do RPO deduction and propagation of function attributes. diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp index 98821bb1408a7..5dc7f6b823365 100644 --- a/llvm/lib/Passes/PassBuilderPipelines.cpp +++ b/llvm/lib/Passes/PassBuilderPipelines.cpp @@ -970,7 +970,8 @@ PassBuilder::buildInlinerPipeline(OptimizationLevel Level, // simplification pipeline, so this only needs to run when it could affect the // function simplification pipeline, which is only the case with recursive // functions. - MainCGPipeline.addPass(PostOrderFunctionAttrsPass(/*SkipNonRecursive*/ true)); + MainCGPipeline.addPass(PostOrderFunctionAttrsPass( + /*SkipNonRecursive*/ true, Phase == ThinOrFullLTOPhase::FullLTOPostLink)); // When at O3 add argument promotion to the pass pipeline. // FIXME: It isn't at all clear why this should be limited to O3. @@ -992,7 +993,8 @@ PassBuilder::buildInlinerPipeline(OptimizationLevel Level, // Finally, deduce any function attributes based on the fully simplified // function. - MainCGPipeline.addPass(PostOrderFunctionAttrsPass()); + MainCGPipeline.addPass(PostOrderFunctionAttrsPass( + false, Phase == ThinOrFullLTOPhase::FullLTOPostLink)); // Mark that the function is fully simplified and that it shouldn't be // simplified again if we somehow revisit it due to CGSCC mutations unless @@ -1914,7 +1916,7 @@ PassBuilder::buildLTODefaultPipeline(OptimizationLevel Level, // Promoting by-reference arguments to by-value exposes more constants to // IPSCCP. CGSCCPassManager CGPM; - CGPM.addPass(PostOrderFunctionAttrsPass()); + CGPM.addPass(PostOrderFunctionAttrsPass(false, true)); CGPM.addPass(ArgumentPromotionPass()); CGPM.addPass( createCGSCCToFunctionPassAdaptor(SROAPass(SROAOptions::ModifyCFG))); @@ -2076,8 +2078,8 @@ PassBuilder::buildLTODefaultPipeline(OptimizationLevel Level, MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM), PTO.EagerlyInvalidateAnalyses)); - MPM.addPass( - createModuleToPostOrderCGSCCPassAdaptor(PostOrderFunctionAttrsPass())); + MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor( + PostOrderFunctionAttrsPass(false, true))); // Require the GlobalsAA analysis for the module so we can query it within // MainFPM. diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp index 8d9a0e7eaef63..9184c65e12539 100644 --- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp +++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp @@ -109,6 +109,11 @@ static cl::opt DisableThinLTOPropagation( "disable-thinlto-funcattrs", cl::init(true), cl::Hidden, cl::desc("Don't propagate function-attrs in thinLTO")); +static cl::opt ForceLTOFuncAttrs( + "force-lto-funcattrs", cl::init(false), cl::Hidden, + cl::desc("Force LTO behaviour for function-attrs pass. Intended for testing" + "purposes only")); + static void addCapturesStat(CaptureInfo CI) { if (capturesNothing(CI)) ++NumCapturesNone; @@ -2068,7 +2073,8 @@ static void inferAttrsFromFunctionBodies(const SCCNodeSet &SCCNodes, } static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes, - SmallPtrSet &Changed) { + SmallPtrSet &Changed, + bool NoFunctionsAddressIsTaken) { // Try and identify functions that do not recurse. // If the SCC contains multiple nodes we know for sure there is recursion. @@ -2079,24 +2085,38 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes, if (!F || !F->hasExactDefinition() || F->doesNotRecurse()) return; - // If all of the calls in F are identifiable and are to norecurse functions, F - // is norecurse. This check also detects self-recursion as F is not currently - // marked norecurse, so any called from F to F will not be marked norecurse. - for (auto &BB : *F) - for (auto &I : BB.instructionsWithoutDebug()) + // If all of the calls in F are identifiable and can be proven to not + // callback F, F is norecurse. This check also detects self-recursion + // as F is not currently marked norecurse, so any call from F to F + // will not be marked norecurse. + for (auto &BB : *F) { + for (auto &I : BB.instructionsWithoutDebug()) { if (auto *CB = dyn_cast(&I)) { Function *Callee = CB->getCalledFunction(); - if (!Callee || Callee == F || - (!Callee->doesNotRecurse() && - !(Callee->isDeclaration() && - Callee->hasFnAttribute(Attribute::NoCallback)))) - // Function calls a potentially recursive function. + + if (!Callee || Callee == F) return; + + if (Callee->doesNotRecurse()) + continue; + + // If there are no functions with external linkage and none of the + // functions' address is taken, it ensures that this Callee does not + // have any path leading back to the Caller F. + // The 'NoFunctionsAddressIsTaken' flag is only set during post-link + // LTO phase after examining all available function definitions. + if (NoFunctionsAddressIsTaken || + (Callee->isDeclaration() && + Callee->hasFnAttribute(Attribute::NoCallback))) + continue; + return; } + } + } - // Every call was to a non-recursive function other than this function, and - // we have no indirect recursion as the SCC size is one. This function cannot - // recurse. + // Every call was either to an external function guaranteed to not make a + // call to this function or a direct call to internal function. Also, SCC is + // one. Together, the above checks ensures, this function cannot recurse. F->setDoesNotRecurse(); ++NumNoRecurse; Changed.insert(F); @@ -2240,7 +2260,8 @@ static SCCNodesResult createSCCNodeSet(ArrayRef Functions) { template static SmallPtrSet deriveAttrsInPostOrder(ArrayRef Functions, AARGetterT &&AARGetter, - bool ArgAttrsOnly) { + bool ArgAttrsOnly, + bool NoFunctionAddressIsTaken = false) { SCCNodesResult Nodes = createSCCNodeSet(Functions); // Bail if the SCC only contains optnone functions. @@ -2267,7 +2288,7 @@ deriveAttrsInPostOrder(ArrayRef Functions, AARGetterT &&AARGetter, addNoAliasAttrs(Nodes.SCCNodes, Changed); addNonNullAttrs(Nodes.SCCNodes, Changed); inferAttrsFromFunctionBodies(Nodes.SCCNodes, Changed); - addNoRecurseAttrs(Nodes.SCCNodes, Changed); + addNoRecurseAttrs(Nodes.SCCNodes, Changed, NoFunctionAddressIsTaken); // Finally, infer the maximal set of attributes from the ones we've inferred // above. This is handling the cases where one attribute on a signature @@ -2309,8 +2330,38 @@ PreservedAnalyses PostOrderFunctionAttrsPass::run(LazyCallGraph::SCC &C, Functions.push_back(&N.getFunction()); } - auto ChangedFunctions = - deriveAttrsInPostOrder(Functions, AARGetter, ArgAttrsOnly); + // Check if any function in the whole program has its address taken or has + // potentially external linkage. + // We use this information when inferring norecurse attribute: If there is + // no function whose address is taken and all functions have internal + // linkage, there is no path for a callback to any user function. + bool AnyFunctionsAddressIsTaken = false; + // Get the parent Module of the Function + Module &M = *C.begin()->getFunction().getParent(); + for (Function &F : M) { + // We only care about functions defined in user program whose addresses + // escape, making them potential callback targets. + if (F.isDeclaration()) + continue; + + // If the function is already marked as norecurse, this should not block + // norecurse inference even though it may have external linkage. + // For ex: main() in C++. + if (F.doesNotRecurse()) + continue; + + if (!F.hasLocalLinkage() || F.hasAddressTaken()) { + AnyFunctionsAddressIsTaken = true; + break; // break if we found one + } + } + + if (ForceLTOFuncAttrs) + IsLTOPostLink = true; // For testing purposes only + auto ChangedFunctions = deriveAttrsInPostOrder( + Functions, AARGetter, ArgAttrsOnly, + IsLTOPostLink ? !AnyFunctionsAddressIsTaken : false); + if (ChangedFunctions.empty()) return PreservedAnalyses::all(); diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_address_taken.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_address_taken.ll new file mode 100644 index 0000000000000..f1a13aa4cf9c4 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_address_taken.ll @@ -0,0 +1,34 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5 +; RUN: opt < %s -passes=function-attrs -force-lto-funcattrs -S | FileCheck %s + +; This test includes a call to a library function which is not marked as +; NoCallback. Function bob() does not have internal linkage and hence prevents +; norecurse to be added. + +@.str = private unnamed_addr constant [12 x i8] c"Hello World\00", align 1 + +define dso_local void @bob() { +; CHECK-LABEL: define dso_local void @bob() { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[CALL:%.*]] = tail call i32 (ptr, ...) @printf(ptr noundef nonnull dereferenceable(1) @.str) +; CHECK-NEXT: ret void +; +entry: + %call = tail call i32 (ptr, ...) @printf(ptr noundef nonnull dereferenceable(1) @.str) + ret void +} + +declare noundef i32 @printf(ptr noundef readonly captures(none), ...) + +define dso_local noundef i32 @main() norecurse { +; CHECK: Function Attrs: norecurse +; CHECK-LABEL: define dso_local noundef i32 @main( +; CHECK-SAME: ) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bob() +; CHECK-NEXT: ret i32 0 +; +entry: + tail call void @bob() + ret i32 0 +} diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_no_address_taken.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_no_address_taken.ll new file mode 100644 index 0000000000000..cf32676527ed2 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_no_address_taken.ll @@ -0,0 +1,39 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5 +; RUN: opt < %s -passes=function-attrs -force-lto-funcattrs -S | FileCheck %s + +; This test includes a call to a library function which is not marked as +; NoCallback. All functions except main() are internal and main is marked +; norecurse, so as to not block norecurse to be added to bob(). + +@.str = private unnamed_addr constant [12 x i8] c"Hello World\00", align 1 + +; Function Attrs: nofree noinline nounwind uwtable +define internal void @bob() { +; CHECK: Function Attrs: norecurse +; CHECK-LABEL: define internal void @bob( +; CHECK-SAME: ) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[CALL:%.*]] = tail call i32 (ptr, ...) @printf(ptr noundef nonnull dereferenceable(1) @.str) +; CHECK-NEXT: ret void +; +entry: + %call = tail call i32 (ptr, ...) @printf(ptr noundef nonnull dereferenceable(1) @.str) + ret void +} + +; Function Attrs: nofree nounwind +declare noundef i32 @printf(ptr noundef readonly captures(none), ...) + +; Function Attrs: nofree norecurse nounwind uwtable +define dso_local noundef i32 @main() norecurse { +; CHECK: Function Attrs: norecurse +; CHECK-LABEL: define dso_local noundef i32 @main( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bob() +; CHECK-NEXT: ret i32 0 +; +entry: + tail call void @bob() + ret i32 0 +} diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion.ll new file mode 100644 index 0000000000000..ed01b88db56b6 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion.ll @@ -0,0 +1,168 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5 +; RUN: opt < %s -passes=function-attrs -force-lto-funcattrs -S | FileCheck %s + +; This test includes a call graph with multiple SCCs. The purpose of this is +; to check that norecurse is not added when a function is part of non-singular +; SCC. +; There are three different SCCs in this test: +; SCC#1: f1, foo, bar, foo1, bar1 +; SCC#2: bar2, bar3, bar4 +; SCC#3: baz, fun +; None of these functions should be marked as norecurse + +; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable +define internal void @bar1() { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define internal void @bar1( +; CHECK-SAME: ) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @f1() +; CHECK-NEXT: ret void +; +entry: + tail call void @f1() + ret void +} + +; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable +define internal void @f1() { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define internal void @f1( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @foo() +; CHECK-NEXT: tail call void @bar2() +; CHECK-NEXT: tail call void @baz() +; CHECK-NEXT: ret void +; +entry: + tail call void @foo() + tail call void @bar2() + tail call void @baz() + ret void +} + +; Function Attrs: nofree noinline norecurse nosync nounwind memory(none) uwtable +define dso_local noundef i32 @main() norecurse { +; CHECK: Function Attrs: nofree norecurse nosync nounwind memory(none) +; CHECK-LABEL: define dso_local noundef i32 @main( +; CHECK-SAME: ) #[[ATTR1:[0-9]+]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @f1() +; CHECK-NEXT: ret i32 0 +; +entry: + tail call void @f1() + ret i32 0 +} + +; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable +define internal void @foo1() { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define internal void @foo1( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar1() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar1() + ret void +} + +; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable +define internal void @bar() { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define internal void @bar( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @foo1() +; CHECK-NEXT: ret void +; +entry: + tail call void @foo1() + ret void +} + +; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable +define internal void @foo() { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define internal void @foo( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar() + ret void +} + +; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable +define internal void @bar4() { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define internal void @bar4( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar2() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar2() + ret void +} + +; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable +define internal void @bar2() { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define internal void @bar2( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar3() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar3() + ret void +} + +; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable +define internal void @bar3() { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define internal void @bar3( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar4() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar4() + ret void +} + +; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable +define internal void @fun() { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define internal void @fun( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @baz() +; CHECK-NEXT: ret void +; +entry: + tail call void @baz() + ret void +} + +define internal void @baz() { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define internal void @baz( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @fun() +; CHECK-NEXT: ret void +; +entry: + tail call void @fun() + ret void +} diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion1.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion1.ll new file mode 100644 index 0000000000000..4004ed0f1d40b --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion1.ll @@ -0,0 +1,112 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5 +; RUN: opt < %s -passes=function-attrs -force-lto-funcattrs -S | FileCheck %s + +; This test includes a call graph with multiple SCCs. The purpose of this is +; to check that norecurse is added to a function which calls a function which +; is indirectly recursive but is not part of the recursive chain. +; There are two SCCs in this test: +; SCC#1: bar2, bar3, bar4 +; SCC#2: baz, fun +; f1() calls bar2 and baz, both of which are part of some indirect recursive +; chain. but does not call back f1() and hence f1() can be marked as +; norecurse. + +; Function Attrs: nofree noinline norecurse nosync nounwind memory(none) uwtable +define dso_local noundef i32 @main() norecurse { +; CHECK: Function Attrs: nofree norecurse nosync nounwind memory(none) +; CHECK-LABEL: define dso_local noundef i32 @main( +; CHECK-SAME: ) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @f1() +; CHECK-NEXT: ret i32 0 +; +entry: + tail call void @f1() + ret i32 0 +} + +; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable +define internal void @f1() { +; CHECK: Function Attrs: nofree norecurse nosync nounwind memory(none) +; CHECK-LABEL: define internal void @f1( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar2() +; CHECK-NEXT: tail call void @baz() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar2() + tail call void @baz() + ret void +} + +; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable +define internal void @bar4() { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define internal void @bar4( +; CHECK-SAME: ) #[[ATTR1:[0-9]+]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar2() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar2() + ret void +} + +; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable +define internal void @bar2() { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define internal void @bar2( +; CHECK-SAME: ) #[[ATTR1]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar3() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar3() + ret void +} + +; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable +define internal void @bar3() { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define internal void @bar3( +; CHECK-SAME: ) #[[ATTR1]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar4() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar4() + ret void +} + +; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable +define internal void @fun() { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define internal void @fun( +; CHECK-SAME: ) #[[ATTR1]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @baz() +; CHECK-NEXT: ret void +; +entry: + tail call void @baz() + ret void +} + +; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable +define internal void @baz() { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define internal void @baz( +; CHECK-SAME: ) #[[ATTR1]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @fun() +; CHECK-NEXT: ret void +; +entry: + tail call void @fun() + ret void +} diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll new file mode 100644 index 0000000000000..f4a809335e1e9 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll @@ -0,0 +1,136 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5 +; RUN: opt < %s -passes=function-attrs -force-lto-funcattrs -S | FileCheck %s + +; This test includes a call graph with a self recursive function. +; The purpose of this is to check that norecurse is added to functions +; which have a self-recursive function in the call-chain. +; The call-chain in this test is as follows +; main -> bob -> callee1 -> callee2 -> callee3 -> callee4 -> callee5 +; where callee5 is self recursive. + +@x = dso_local global i32 4, align 4 +@y = dso_local global i32 2, align 4 + +define internal void @callee6() { +; CHECK: Function Attrs: nofree norecurse nounwind memory(readwrite, argmem: none) +; CHECK-LABEL: define internal void @callee6( +; CHECK-SAME: ) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[TMP0:%.*]] = load volatile i32, ptr @y, align 4 +; CHECK-NEXT: [[INC:%.*]] = add nsw i32 [[TMP0]], 1 +; CHECK-NEXT: store volatile i32 [[INC]], ptr @y, align 4 +; CHECK-NEXT: ret void +; +entry: + %0 = load volatile i32, ptr @y, align 4 + %inc = add nsw i32 %0, 1 + store volatile i32 %inc, ptr @y, align 4 + ret void +} + +define internal void @callee5(i32 noundef %x) { +; CHECK: Function Attrs: nofree nounwind +; CHECK-LABEL: define internal void @callee5( +; CHECK-SAME: i32 noundef [[X:%.*]]) #[[ATTR1:[0-9]+]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i32 [[X]], 0 +; CHECK-NEXT: br i1 [[CMP]], label %[[IF_THEN:.*]], label %[[IF_END:.*]] +; CHECK: [[IF_THEN]]: +; CHECK-NEXT: tail call void @callee5(i32 noundef [[X]]) +; CHECK-NEXT: br label %[[IF_END]] +; CHECK: [[IF_END]]: +; CHECK-NEXT: tail call void @callee6() +; CHECK-NEXT: ret void +; +entry: + %cmp = icmp sgt i32 %x, 0 + br i1 %cmp, label %if.then, label %if.end + +if.then: ; preds = %entry + tail call void @callee5(i32 noundef %x) + br label %if.end + +if.end: ; preds = %if.then, %entry + tail call void @callee6() + ret void +} + +define internal void @callee4() { +; CHECK: Function Attrs: nofree norecurse nounwind +; CHECK-LABEL: define internal void @callee4( +; CHECK-SAME: ) #[[ATTR2:[0-9]+]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[TMP0:%.*]] = load volatile i32, ptr @x, align 4 +; CHECK-NEXT: tail call void @callee5(i32 noundef [[TMP0]]) +; CHECK-NEXT: ret void +; +entry: + %0 = load volatile i32, ptr @x, align 4 + tail call void @callee5(i32 noundef %0) + ret void +} + +define internal void @callee3() { +; CHECK: Function Attrs: nofree norecurse nounwind +; CHECK-LABEL: define internal void @callee3( +; CHECK-SAME: ) #[[ATTR2]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @callee4() +; CHECK-NEXT: ret void +; +entry: + tail call void @callee4() + ret void +} + +define internal void @callee2() { +; CHECK: Function Attrs: nofree norecurse nounwind +; CHECK-LABEL: define internal void @callee2( +; CHECK-SAME: ) #[[ATTR2]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @callee3() +; CHECK-NEXT: ret void +; +entry: + tail call void @callee3() + ret void +} + +define internal void @callee1() { +; CHECK: Function Attrs: nofree norecurse nounwind +; CHECK-LABEL: define internal void @callee1( +; CHECK-SAME: ) #[[ATTR2]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @callee2() +; CHECK-NEXT: ret void +; +entry: + tail call void @callee2() + ret void +} + +define internal void @bob() { +; CHECK: Function Attrs: nofree norecurse nounwind +; CHECK-LABEL: define internal void @bob( +; CHECK-SAME: ) #[[ATTR2]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @callee1() +; CHECK-NEXT: ret void +; +entry: + tail call void @callee1() + ret void +} + +define dso_local noundef i32 @main() norecurse { +; CHECK: Function Attrs: nofree norecurse nounwind +; CHECK-LABEL: define dso_local noundef i32 @main( +; CHECK-SAME: ) #[[ATTR2]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bob() +; CHECK-NEXT: ret i32 0 +; +entry: + tail call void @bob() + ret i32 0 +} diff --git a/llvm/test/Transforms/FunctionAttrs/nounwind.ll b/llvm/test/Transforms/FunctionAttrs/nounwind.ll index 076a7df2781ce..f774fb70c8f2a 100644 --- a/llvm/test/Transforms/FunctionAttrs/nounwind.ll +++ b/llvm/test/Transforms/FunctionAttrs/nounwind.ll @@ -4,15 +4,10 @@ ; TEST 1 define i32 @foo1() { -; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) -; FNATTRS-LABEL: define {{[^@]+}}@foo1 -; FNATTRS-SAME: () #[[ATTR0:[0-9]+]] { -; FNATTRS-NEXT: ret i32 1 -; -; ATTRIBUTOR: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) -; ATTRIBUTOR-LABEL: define {{[^@]+}}@foo1 -; ATTRIBUTOR-SAME: () #[[ATTR0:[0-9]+]] { -; ATTRIBUTOR-NEXT: ret i32 1 +; COMMON: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) +; COMMON-LABEL: define {{[^@]+}}@foo1 +; COMMON-SAME: () #[[ATTR0:[0-9]+]] { +; COMMON-NEXT: ret i32 1 ; ret i32 1 } @@ -75,23 +70,14 @@ define void @call_non_nounwind(){ ; } define i32 @maybe_throw(i1 zeroext %0) { -; FNATTRS-LABEL: define {{[^@]+}}@maybe_throw -; FNATTRS-SAME: (i1 zeroext [[TMP0:%.*]]) { -; FNATTRS-NEXT: br i1 [[TMP0]], label [[TMP2:%.*]], label [[TMP3:%.*]] -; FNATTRS: 2: -; FNATTRS-NEXT: tail call void @__cxa_rethrow() -; FNATTRS-NEXT: unreachable -; FNATTRS: 3: -; FNATTRS-NEXT: ret i32 -1 -; -; ATTRIBUTOR-LABEL: define {{[^@]+}}@maybe_throw -; ATTRIBUTOR-SAME: (i1 zeroext [[TMP0:%.*]]) { -; ATTRIBUTOR-NEXT: br i1 [[TMP0]], label [[TMP2:%.*]], label [[TMP3:%.*]] -; ATTRIBUTOR: 2: -; ATTRIBUTOR-NEXT: tail call void @__cxa_rethrow() -; ATTRIBUTOR-NEXT: unreachable -; ATTRIBUTOR: 3: -; ATTRIBUTOR-NEXT: ret i32 -1 +; COMMON-LABEL: define {{[^@]+}}@maybe_throw +; COMMON-SAME: (i1 zeroext [[TMP0:%.*]]) { +; COMMON-NEXT: br i1 [[TMP0]], label [[TMP2:%.*]], label [[TMP3:%.*]] +; COMMON: 2: +; COMMON-NEXT: tail call void @__cxa_rethrow() +; COMMON-NEXT: unreachable +; COMMON: 3: +; COMMON-NEXT: ret i32 -1 ; br i1 %0, label %2, label %3 @@ -115,31 +101,18 @@ declare void @__cxa_rethrow() ; } define i32 @catch_thing() personality ptr @__gxx_personality_v0 { -; FNATTRS-LABEL: define {{[^@]+}}@catch_thing() personality ptr @__gxx_personality_v0 { -; FNATTRS-NEXT: invoke void @__cxa_rethrow() -; FNATTRS-NEXT: to label [[TMP1:%.*]] unwind label [[TMP2:%.*]] -; FNATTRS: 1: -; FNATTRS-NEXT: unreachable -; FNATTRS: 2: -; FNATTRS-NEXT: [[TMP3:%.*]] = landingpad { ptr, i32 } -; FNATTRS-NEXT: catch ptr null -; FNATTRS-NEXT: [[TMP4:%.*]] = extractvalue { ptr, i32 } [[TMP3]], 0 -; FNATTRS-NEXT: [[TMP5:%.*]] = tail call ptr @__cxa_begin_catch(ptr [[TMP4]]) -; FNATTRS-NEXT: tail call void @__cxa_end_catch() -; FNATTRS-NEXT: ret i32 -1 -; -; ATTRIBUTOR-LABEL: define {{[^@]+}}@catch_thing() personality ptr @__gxx_personality_v0 { -; ATTRIBUTOR-NEXT: invoke void @__cxa_rethrow() -; ATTRIBUTOR-NEXT: to label [[TMP1:%.*]] unwind label [[TMP2:%.*]] -; ATTRIBUTOR: 1: -; ATTRIBUTOR-NEXT: unreachable -; ATTRIBUTOR: 2: -; ATTRIBUTOR-NEXT: [[TMP3:%.*]] = landingpad { ptr, i32 } -; ATTRIBUTOR-NEXT: catch ptr null -; ATTRIBUTOR-NEXT: [[TMP4:%.*]] = extractvalue { ptr, i32 } [[TMP3]], 0 -; ATTRIBUTOR-NEXT: [[TMP5:%.*]] = tail call ptr @__cxa_begin_catch(ptr [[TMP4]]) -; ATTRIBUTOR-NEXT: tail call void @__cxa_end_catch() -; ATTRIBUTOR-NEXT: ret i32 -1 +; COMMON-LABEL: define {{[^@]+}}@catch_thing() personality ptr @__gxx_personality_v0 { +; COMMON-NEXT: invoke void @__cxa_rethrow() +; COMMON-NEXT: to label [[TMP1:%.*]] unwind label [[TMP2:%.*]] +; COMMON: 1: +; COMMON-NEXT: unreachable +; COMMON: 2: +; COMMON-NEXT: [[TMP3:%.*]] = landingpad { ptr, i32 } +; COMMON-NEXT: catch ptr null +; COMMON-NEXT: [[TMP4:%.*]] = extractvalue { ptr, i32 } [[TMP3]], 0 +; COMMON-NEXT: [[TMP5:%.*]] = tail call ptr @__cxa_begin_catch(ptr [[TMP4]]) +; COMMON-NEXT: tail call void @__cxa_end_catch() +; COMMON-NEXT: ret i32 -1 ; invoke void @__cxa_rethrow() #1 to label %1 unwind label %2 @@ -157,13 +130,9 @@ define i32 @catch_thing() personality ptr @__gxx_personality_v0 { } define i32 @catch_thing_user() { -; FNATTRS-LABEL: define {{[^@]+}}@catch_thing_user() { -; FNATTRS-NEXT: [[CATCH_THING_CALL:%.*]] = call i32 @catch_thing() -; FNATTRS-NEXT: ret i32 [[CATCH_THING_CALL]] -; -; ATTRIBUTOR-LABEL: define {{[^@]+}}@catch_thing_user() { -; ATTRIBUTOR-NEXT: [[CATCH_THING_CALL:%.*]] = call i32 @catch_thing() -; ATTRIBUTOR-NEXT: ret i32 [[CATCH_THING_CALL]] +; COMMON-LABEL: define {{[^@]+}}@catch_thing_user() { +; COMMON-NEXT: [[CATCH_THING_CALL:%.*]] = call i32 @catch_thing() +; COMMON-NEXT: ret i32 [[CATCH_THING_CALL]] ; %catch_thing_call = call i32 @catch_thing() ret i32 %catch_thing_call @@ -178,10 +147,10 @@ define void @catch_specific_landingpad() personality ptr @__gxx_personality_v0 { ; COMMON-LABEL: define {{[^@]+}}@catch_specific_landingpad ; COMMON-SAME: () #[[ATTR3:[0-9]+]] personality ptr @__gxx_personality_v0 { ; COMMON-NEXT: invoke void @do_throw() -; COMMON-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]] +; COMMON-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]] ; COMMON: lpad: ; COMMON-NEXT: [[LP:%.*]] = landingpad { ptr, i32 } -; COMMON-NEXT: catch ptr @catch_ty +; COMMON-NEXT: catch ptr @catch_ty ; COMMON-NEXT: call void @abort() ; COMMON-NEXT: unreachable ; COMMON: unreachable: @@ -205,10 +174,10 @@ define void @catch_all_landingpad() personality ptr @__gxx_personality_v0 { ; COMMON-LABEL: define {{[^@]+}}@catch_all_landingpad ; COMMON-SAME: () #[[ATTR4:[0-9]+]] personality ptr @__gxx_personality_v0 { ; COMMON-NEXT: invoke void @do_throw() -; COMMON-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]] +; COMMON-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]] ; COMMON: lpad: ; COMMON-NEXT: [[LP:%.*]] = landingpad { ptr, i32 } -; COMMON-NEXT: catch ptr null +; COMMON-NEXT: catch ptr null ; COMMON-NEXT: call void @abort() ; COMMON-NEXT: unreachable ; COMMON: unreachable: @@ -232,10 +201,10 @@ define void @filter_specific_landingpad() personality ptr @__gxx_personality_v0 ; COMMON-LABEL: define {{[^@]+}}@filter_specific_landingpad ; COMMON-SAME: () #[[ATTR3]] personality ptr @__gxx_personality_v0 { ; COMMON-NEXT: invoke void @do_throw() -; COMMON-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]] +; COMMON-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]] ; COMMON: lpad: ; COMMON-NEXT: [[LP:%.*]] = landingpad { ptr, i32 } -; COMMON-NEXT: filter [1 x ptr] [ptr @catch_ty] +; COMMON-NEXT: filter [1 x ptr] [ptr @catch_ty] ; COMMON-NEXT: call void @abort() ; COMMON-NEXT: unreachable ; COMMON: unreachable: @@ -259,10 +228,10 @@ define void @filter_none_landingpad() personality ptr @__gxx_personality_v0 { ; COMMON-LABEL: define {{[^@]+}}@filter_none_landingpad ; COMMON-SAME: () #[[ATTR4]] personality ptr @__gxx_personality_v0 { ; COMMON-NEXT: invoke void @do_throw() -; COMMON-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]] +; COMMON-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]] ; COMMON: lpad: ; COMMON-NEXT: [[LP:%.*]] = landingpad { ptr, i32 } -; COMMON-NEXT: filter [0 x ptr] zeroinitializer +; COMMON-NEXT: filter [0 x ptr] zeroinitializer ; COMMON-NEXT: call void @abort() ; COMMON-NEXT: unreachable ; COMMON: unreachable: @@ -286,10 +255,10 @@ define void @cleanup_landingpad() personality ptr @__gxx_personality_v0 { ; COMMON-LABEL: define {{[^@]+}}@cleanup_landingpad ; COMMON-SAME: () #[[ATTR3]] personality ptr @__gxx_personality_v0 { ; COMMON-NEXT: invoke void @do_throw() -; COMMON-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]] +; COMMON-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]] ; COMMON: lpad: ; COMMON-NEXT: [[LP:%.*]] = landingpad { ptr, i32 } -; COMMON-NEXT: cleanup +; COMMON-NEXT: cleanup ; COMMON-NEXT: call void @abort() ; COMMON-NEXT: unreachable ; COMMON: unreachable: @@ -313,7 +282,7 @@ define void @cleanuppad() personality ptr @__gxx_personality_v0 { ; FNATTRS-LABEL: define {{[^@]+}}@cleanuppad ; FNATTRS-SAME: () #[[ATTR3]] personality ptr @__gxx_personality_v0 { ; FNATTRS-NEXT: invoke void @do_throw() -; FNATTRS-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[CPAD:%.*]] +; FNATTRS-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[CPAD:%.*]] ; FNATTRS: cpad: ; FNATTRS-NEXT: [[CP:%.*]] = cleanuppad within none [] ; FNATTRS-NEXT: call void @abort() @@ -325,7 +294,7 @@ define void @cleanuppad() personality ptr @__gxx_personality_v0 { ; ATTRIBUTOR-LABEL: define {{[^@]+}}@cleanuppad ; ATTRIBUTOR-SAME: () #[[ATTR4]] personality ptr @__gxx_personality_v0 { ; ATTRIBUTOR-NEXT: invoke void @do_throw() -; ATTRIBUTOR-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[CPAD:%.*]] +; ATTRIBUTOR-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[CPAD:%.*]] ; ATTRIBUTOR: cpad: ; ATTRIBUTOR-NEXT: [[CP:%.*]] = cleanuppad within none [] ; ATTRIBUTOR-NEXT: call void @abort() @@ -350,7 +319,7 @@ define void @catchswitch_cleanuppad() personality ptr @__gxx_personality_v0 { ; FNATTRS-LABEL: define {{[^@]+}}@catchswitch_cleanuppad ; FNATTRS-SAME: () #[[ATTR3]] personality ptr @__gxx_personality_v0 { ; FNATTRS-NEXT: invoke void @do_throw() -; FNATTRS-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[CS:%.*]] +; FNATTRS-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[CS:%.*]] ; FNATTRS: cs: ; FNATTRS-NEXT: [[TOK:%.*]] = catchswitch within none [label %catch] unwind label [[CPAD:%.*]] ; FNATTRS: catch: @@ -368,7 +337,7 @@ define void @catchswitch_cleanuppad() personality ptr @__gxx_personality_v0 { ; ATTRIBUTOR-LABEL: define {{[^@]+}}@catchswitch_cleanuppad ; ATTRIBUTOR-SAME: () #[[ATTR4]] personality ptr @__gxx_personality_v0 { ; ATTRIBUTOR-NEXT: invoke void @do_throw() -; ATTRIBUTOR-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[CS:%.*]] +; ATTRIBUTOR-NEXT: to label [[UNREACHABLE:%.*]] unwind label [[CS:%.*]] ; ATTRIBUTOR: cs: ; ATTRIBUTOR-NEXT: [[TOK:%.*]] = catchswitch within none [label %catch] unwind label [[CPAD:%.*]] ; ATTRIBUTOR: catch: