From 09220c5fb0ae2ab6d90dddb6b24e85dd7be8fd86 Mon Sep 17 00:00:00 2001 From: Usha Gupta Date: Mon, 15 Sep 2025 10:52:48 +0000 Subject: [PATCH 1/3] [FuncAttrs][LTO] Relax norecurse attribute inference during postlink LTO --- .../llvm/Transforms/IPO/FunctionAttrs.h | 13 ++ llvm/lib/Passes/PassBuilderPipelines.cpp | 3 +- llvm/lib/Passes/PassRegistry.def | 1 + llvm/lib/Transforms/IPO/FunctionAttrs.cpp | 123 +++++++++++++--- llvm/test/Other/new-pm-lto-defaults.ll | 1 + .../norecurse_libfunc_address_taken.ll | 34 +++++ .../norecurse_libfunc_no_address_taken.ll | 39 +++++ .../norecurse_multi_scc_indirect_recursion.ll | 138 ++++++++++++++++++ ...norecurse_multi_scc_indirect_recursion1.ll | 95 ++++++++++++ .../norecurse_self_recursive_callee.ll | 135 +++++++++++++++++ 10 files changed, 559 insertions(+), 23 deletions(-) create mode 100644 llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_address_taken.ll create mode 100644 llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_no_address_taken.ll create mode 100644 llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion.ll create mode 100644 llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion1.ll create mode 100644 llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll diff --git a/llvm/include/llvm/Transforms/IPO/FunctionAttrs.h b/llvm/include/llvm/Transforms/IPO/FunctionAttrs.h index 754714dceb7a6..eaca0a8fdac0b 100644 --- a/llvm/include/llvm/Transforms/IPO/FunctionAttrs.h +++ b/llvm/include/llvm/Transforms/IPO/FunctionAttrs.h @@ -79,6 +79,19 @@ class ReversePostOrderFunctionAttrsPass LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); }; +/// Additional 'norecurse' attribute deduction during postlink LTO phase. +/// +/// This is a module pass that infers 'norecurse' attribute on functions. +/// It runs during LTO and analyzes the module's call graph to find functions +/// that are guaranteed not to call themselves, either directly or indirectly. +/// The pass uses a module-wide flag which checks if any function's address is +/// taken or any function in the module has external linkage, to safely handle +/// indirect and library function calls from current function. +class NoRecurseLTOInferencePass + : public PassInfoMixin { +public: + LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM); +}; } // end namespace llvm #endif // LLVM_TRANSFORMS_IPO_FUNCTIONATTRS_H diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp index 98821bb1408a7..ca16f2e580e85 100644 --- a/llvm/lib/Passes/PassBuilderPipelines.cpp +++ b/llvm/lib/Passes/PassBuilderPipelines.cpp @@ -1944,6 +1944,7 @@ PassBuilder::buildLTODefaultPipeline(OptimizationLevel Level, // is fixed. MPM.addPass(WholeProgramDevirtPass(ExportSummary, nullptr)); + MPM.addPass(NoRecurseLTOInferencePass()); // Stop here at -O1. if (Level == OptimizationLevel::O1) { // The LowerTypeTestsPass needs to run to lower type metadata and the @@ -2355,4 +2356,4 @@ AAManager PassBuilder::buildDefaultAAPipeline() { bool PassBuilder::isInstrumentedPGOUse() const { return (PGOOpt && PGOOpt->Action == PGOOptions::IRUse) || !UseCtxProfile.empty(); -} \ No newline at end of file +} diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def index 1d015971dfbdf..2acfd3c1789c8 100644 --- a/llvm/lib/Passes/PassRegistry.def +++ b/llvm/lib/Passes/PassRegistry.def @@ -119,6 +119,7 @@ MODULE_PASS("metarenamer", MetaRenamerPass()) MODULE_PASS("module-inline", ModuleInlinerPass()) MODULE_PASS("name-anon-globals", NameAnonGlobalPass()) MODULE_PASS("no-op-module", NoOpModulePass()) +MODULE_PASS("norecurse-lto-inference", NoRecurseLTOInferencePass()) MODULE_PASS("nsan", NumericalStabilitySanitizerPass()) MODULE_PASS("openmp-opt", OpenMPOptPass()) MODULE_PASS("openmp-opt-postlink", diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp index 8d9a0e7eaef63..b5f92d8c3cf65 100644 --- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp +++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp @@ -2067,6 +2067,36 @@ static void inferAttrsFromFunctionBodies(const SCCNodeSet &SCCNodes, AI.run(SCCNodes, Changed); } +// Determines if the function 'F' can be marked 'norecurse'. +// It returns true if any call within 'F' could lead to a recursive +// call back to 'F', and false otherwise. +// The 'AnyFunctionsAddressIsTaken' parameter is a module-wide flag +// that is true if any function's address is taken, or if any function +// has external linkage. This is used to determine the safety of +// external/library calls. +static bool hasRecursiveCallee(Function &F, + bool AnyFunctionsAddressIsTaken = true) { + for (const auto &BB : F) { + for (const auto &I : BB.instructionsWithoutDebug()) { + if (const auto *CB = dyn_cast(&I)) { + const Function *Callee = CB->getCalledFunction(); + if (!Callee || Callee == &F) + return true; + + if (Callee->doesNotRecurse()) + continue; + + if (!AnyFunctionsAddressIsTaken || + (Callee->isDeclaration() && + Callee->hasFnAttribute(Attribute::NoCallback))) + continue; + return true; + } + } + } + return false; +} + static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes, SmallPtrSet &Changed) { // Try and identify functions that do not recurse. @@ -2078,28 +2108,14 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes, Function *F = *SCCNodes.begin(); 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 (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. - 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. - F->setDoesNotRecurse(); - ++NumNoRecurse; - Changed.insert(F); + if (!hasRecursiveCallee(*F)) { + // 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. + F->setDoesNotRecurse(); + ++NumNoRecurse; + Changed.insert(F); + } } // Set the noreturn function attribute if possible. @@ -2429,3 +2445,66 @@ ReversePostOrderFunctionAttrsPass::run(Module &M, ModuleAnalysisManager &AM) { PA.preserve(); return PA; } + +PreservedAnalyses NoRecurseLTOInferencePass::run(Module &M, + ModuleAnalysisManager &MAM) { + + // 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; + for (Function &F : M) { + if (F.isDeclaration() || F.doesNotRecurse()) { + continue; + } + if (!F.hasLocalLinkage() || F.hasAddressTaken()) { + AnyFunctionsAddressIsTaken = true; + break; + } + } + + // Run norecurse inference on all RefSCCs in the LazyCallGraph for this + // module. + bool Changed = false; + LazyCallGraph &CG = MAM.getResult(M); + CG.buildRefSCCs(); + + for (LazyCallGraph::RefSCC &RC : CG.postorder_ref_sccs()) { + // Skip any RefSCC that is part of a call cycle. A RefSCC containing more + // than one SCC indicates a recursive relationship, which could involve + // direct or indirect calls. + if (RC.size() > 1) { + continue; + } + + // A single-SCC RefSCC could still be a self-loop. + LazyCallGraph::SCC &S = *RC.begin(); + if (S.size() > 1) { + continue; + } + + // Get the single function from this SCC. + Function &F = S.begin()->getFunction(); + if (!F.hasExactDefinition() || F.doesNotRecurse()) { + continue; + } + + // If the analysis confirms that this function has no recursive calls + // (either direct, indirect, or through external linkages), + // we can safely apply the norecurse attribute. + if (!hasRecursiveCallee(F, AnyFunctionsAddressIsTaken)) { + F.setDoesNotRecurse(); + ++NumNoRecurse; + Changed = true; + } + } + + PreservedAnalyses PA; + if (Changed) + PA.preserve(); + else + PA = PreservedAnalyses::all(); + return PA; +} diff --git a/llvm/test/Other/new-pm-lto-defaults.ll b/llvm/test/Other/new-pm-lto-defaults.ll index 3aea0f2061f3e..f595dfe1d6845 100644 --- a/llvm/test/Other/new-pm-lto-defaults.ll +++ b/llvm/test/Other/new-pm-lto-defaults.ll @@ -67,6 +67,7 @@ ; CHECK-O1-NEXT: Running analysis: TargetLibraryAnalysis ; CHECK-O-NEXT: Running pass: GlobalSplitPass ; CHECK-O-NEXT: Running pass: WholeProgramDevirtPass +; CHECK-O-NEXT: Running pass: NoRecurseLTOInferencePass ; CHECK-O23SZ-NEXT: Running pass: CoroEarlyPass ; CHECK-O1-NEXT: Running pass: LowerTypeTestsPass ; CHECK-O23SZ-NEXT: Running pass: GlobalOptPass 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..0ec36f8147872 --- /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=norecurse-lto-inference -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..6d13d5262f9f7 --- /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=norecurse-lto-inference -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..8264cf33df4eb --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion.ll @@ -0,0 +1,138 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5 +; RUN: opt < %s -passes=norecurse-lto-inference -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 + +define internal void @bar1() { +; CHECK-LABEL: define internal void @bar1() { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @f1() +; CHECK-NEXT: ret void +; +entry: + tail call void @f1() + ret void +} + +define internal void @f1() { +; CHECK-LABEL: define internal void @f1() { +; 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 +} + +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 @f1() +; CHECK-NEXT: ret i32 0 +; +entry: + tail call void @f1() + ret i32 0 +} + +define internal void @foo1() { +; CHECK-LABEL: define internal void @foo1() { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar1() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar1() + ret void +} + +define internal void @bar() { +; CHECK-LABEL: define internal void @bar() { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @foo1() +; CHECK-NEXT: ret void +; +entry: + tail call void @foo1() + ret void +} + +define internal void @foo() { +; CHECK-LABEL: define internal void @foo() { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar() + ret void +} + +define internal void @bar4() { +; CHECK-LABEL: define internal void @bar4() { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar2() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar2() + ret void +} + +define internal void @bar2() { +; CHECK-LABEL: define internal void @bar2() { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar3() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar3() + ret void +} + +define internal void @bar3() { +; CHECK-LABEL: define internal void @bar3() { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar4() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar4() + ret void +} + +define internal void @fun() { +; CHECK-LABEL: define internal void @fun() { +; 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-LABEL: define internal void @baz() { +; 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..af986eff3e13c --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion1.ll @@ -0,0 +1,95 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5 +; RUN: opt < %s -passes=norecurse-lto-inference -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. + +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 @f1() +; CHECK-NEXT: ret i32 0 +; +entry: + tail call void @f1() + ret i32 0 +} + +define internal void @f1() { +; CHECK: Function Attrs: norecurse +; 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 +} + +define internal void @bar4() { +; CHECK-LABEL: define internal void @bar4() { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar2() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar2() + ret void +} + +define internal void @bar2() { +; CHECK-LABEL: define internal void @bar2() { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar3() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar3() + ret void +} + +define internal void @bar3() { +; CHECK-LABEL: define internal void @bar3() { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: tail call void @bar4() +; CHECK-NEXT: ret void +; +entry: + tail call void @bar4() + ret void +} + +define internal void @fun() { +; CHECK-LABEL: define internal void @fun() { +; 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-LABEL: define internal void @baz() { +; 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..554642ff6963c --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll @@ -0,0 +1,135 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5 +; RUN: opt < %s -passes=norecurse-lto-inference -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: norecurse +; 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-LABEL: define internal void @callee5( +; CHECK-SAME: i32 noundef [[X:%.*]]) { +; 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: norecurse +; CHECK-LABEL: define internal void @callee4( +; CHECK-SAME: ) #[[ATTR0]] { +; 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: norecurse +; CHECK-LABEL: define internal void @callee3( +; CHECK-SAME: ) #[[ATTR0]] { +; 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: norecurse +; CHECK-LABEL: define internal void @callee2( +; CHECK-SAME: ) #[[ATTR0]] { +; 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: norecurse +; CHECK-LABEL: define internal void @callee1( +; CHECK-SAME: ) #[[ATTR0]] { +; 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: norecurse +; CHECK-LABEL: define internal void @bob( +; CHECK-SAME: ) #[[ATTR0]] { +; 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: 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 +} From d7127b6c7578e1e3ba80d41fc5a97cdb910d6982 Mon Sep 17 00:00:00 2001 From: Usha Gupta Date: Thu, 18 Sep 2025 14:48:34 +0000 Subject: [PATCH 2/3] Update test files as per comments --- llvm/lib/Transforms/IPO/FunctionAttrs.cpp | 20 ++-- .../norecurse_libfunc_address_taken.ll | 18 ++-- .../norecurse_libfunc_no_address_taken.ll | 18 ++-- .../norecurse_multi_scc_indirect_recursion.ll | 9 +- ...norecurse_multi_scc_indirect_recursion1.ll | 9 +- .../norecurse_self_recursive_callee.ll | 97 +++++-------------- 6 files changed, 69 insertions(+), 102 deletions(-) diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp index b5f92d8c3cf65..ccf2fafb4493b 100644 --- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp +++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp @@ -2074,8 +2074,8 @@ static void inferAttrsFromFunctionBodies(const SCCNodeSet &SCCNodes, // that is true if any function's address is taken, or if any function // has external linkage. This is used to determine the safety of // external/library calls. -static bool hasRecursiveCallee(Function &F, - bool AnyFunctionsAddressIsTaken = true) { +static bool mayHaveRecursiveCallee(Function &F, + bool AnyFunctionsAddressIsTaken = true) { for (const auto &BB : F) { for (const auto &I : BB.instructionsWithoutDebug()) { if (const auto *CB = dyn_cast(&I)) { @@ -2108,7 +2108,7 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes, Function *F = *SCCNodes.begin(); if (!F || !F->hasExactDefinition() || F->doesNotRecurse()) return; - if (!hasRecursiveCallee(*F)) { + if (!mayHaveRecursiveCallee(*F)) { // 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. @@ -2456,9 +2456,8 @@ PreservedAnalyses NoRecurseLTOInferencePass::run(Module &M, // linkage, there is no path for a callback to any user function. bool AnyFunctionsAddressIsTaken = false; for (Function &F : M) { - if (F.isDeclaration() || F.doesNotRecurse()) { + if (F.isDeclaration() || F.doesNotRecurse()) continue; - } if (!F.hasLocalLinkage() || F.hasAddressTaken()) { AnyFunctionsAddressIsTaken = true; break; @@ -2475,26 +2474,23 @@ PreservedAnalyses NoRecurseLTOInferencePass::run(Module &M, // Skip any RefSCC that is part of a call cycle. A RefSCC containing more // than one SCC indicates a recursive relationship, which could involve // direct or indirect calls. - if (RC.size() > 1) { + if (RC.size() > 1) continue; - } // A single-SCC RefSCC could still be a self-loop. LazyCallGraph::SCC &S = *RC.begin(); - if (S.size() > 1) { + if (S.size() > 1) continue; - } // Get the single function from this SCC. Function &F = S.begin()->getFunction(); - if (!F.hasExactDefinition() || F.doesNotRecurse()) { + if (!F.hasExactDefinition() || F.doesNotRecurse()) continue; - } // If the analysis confirms that this function has no recursive calls // (either direct, indirect, or through external linkages), // we can safely apply the norecurse attribute. - if (!hasRecursiveCallee(F, AnyFunctionsAddressIsTaken)) { + if (!mayHaveRecursiveCallee(F, AnyFunctionsAddressIsTaken)) { F.setDoesNotRecurse(); ++NumNoRecurse; Changed = true; diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_address_taken.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_address_taken.ll index 0ec36f8147872..bcdf75b021866 100644 --- a/llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_address_taken.ll +++ b/llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_address_taken.ll @@ -1,4 +1,4 @@ -; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5 +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals all --version 5 ; RUN: opt < %s -passes=norecurse-lto-inference -S | FileCheck %s ; This test includes a call to a library function which is not marked as @@ -7,22 +7,25 @@ @.str = private unnamed_addr constant [12 x i8] c"Hello World\00", align 1 +;. +; CHECK: @.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: [[CALL:%.*]] = tail call i32 (ptr, ...) @printf(ptr nonnull dereferenceable(1) @.str) ; CHECK-NEXT: ret void ; entry: - %call = tail call i32 (ptr, ...) @printf(ptr noundef nonnull dereferenceable(1) @.str) + %call = tail call i32 (ptr, ...) @printf(ptr nonnull dereferenceable(1) @.str) ret void } -declare noundef i32 @printf(ptr noundef readonly captures(none), ...) +declare i32 @printf(ptr readonly captures(none), ...) -define dso_local noundef i32 @main() norecurse { +define dso_local i32 @main() norecurse { ; CHECK: Function Attrs: norecurse -; CHECK-LABEL: define dso_local noundef i32 @main( +; CHECK-LABEL: define dso_local i32 @main( ; CHECK-SAME: ) #[[ATTR0:[0-9]+]] { ; CHECK-NEXT: [[ENTRY:.*:]] ; CHECK-NEXT: tail call void @bob() @@ -32,3 +35,6 @@ entry: tail call void @bob() ret i32 0 } +;. +; CHECK: attributes #[[ATTR0]] = { norecurse } +;. diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_no_address_taken.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_no_address_taken.ll index 6d13d5262f9f7..a03b4ca635b1e 100644 --- a/llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_no_address_taken.ll +++ b/llvm/test/Transforms/FunctionAttrs/norecurse_libfunc_no_address_taken.ll @@ -1,4 +1,4 @@ -; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5 +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals all --version 5 ; RUN: opt < %s -passes=norecurse-lto-inference -S | FileCheck %s ; This test includes a call to a library function which is not marked as @@ -8,26 +8,29 @@ @.str = private unnamed_addr constant [12 x i8] c"Hello World\00", align 1 ; Function Attrs: nofree noinline nounwind uwtable +;. +; CHECK: @.str = private unnamed_addr constant [12 x i8] c"Hello World\00", align 1 +;. 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: [[CALL:%.*]] = tail call i32 (ptr, ...) @printf(ptr nonnull dereferenceable(1) @.str) ; CHECK-NEXT: ret void ; entry: - %call = tail call i32 (ptr, ...) @printf(ptr noundef nonnull dereferenceable(1) @.str) + %call = tail call i32 (ptr, ...) @printf(ptr nonnull dereferenceable(1) @.str) ret void } ; Function Attrs: nofree nounwind -declare noundef i32 @printf(ptr noundef readonly captures(none), ...) +declare i32 @printf(ptr readonly captures(none), ...) ; Function Attrs: nofree norecurse nounwind uwtable -define dso_local noundef i32 @main() norecurse { +define dso_local i32 @main() norecurse { ; CHECK: Function Attrs: norecurse -; CHECK-LABEL: define dso_local noundef i32 @main( +; CHECK-LABEL: define dso_local i32 @main( ; CHECK-SAME: ) #[[ATTR0]] { ; CHECK-NEXT: [[ENTRY:.*:]] ; CHECK-NEXT: tail call void @bob() @@ -37,3 +40,6 @@ entry: tail call void @bob() ret i32 0 } +;. +; CHECK: attributes #[[ATTR0]] = { norecurse } +;. diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion.ll index 8264cf33df4eb..e351f60cba2db 100644 --- a/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion.ll +++ b/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion.ll @@ -1,4 +1,4 @@ -; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5 +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals all --version 5 ; RUN: opt < %s -passes=norecurse-lto-inference -S | FileCheck %s ; This test includes a call graph with multiple SCCs. The purpose of this is @@ -36,9 +36,9 @@ entry: ret void } -define dso_local noundef i32 @main() norecurse { +define dso_local i32 @main() norecurse { ; CHECK: Function Attrs: norecurse -; CHECK-LABEL: define dso_local noundef i32 @main( +; CHECK-LABEL: define dso_local i32 @main( ; CHECK-SAME: ) #[[ATTR0:[0-9]+]] { ; CHECK-NEXT: [[ENTRY:.*:]] ; CHECK-NEXT: tail call void @f1() @@ -136,3 +136,6 @@ entry: tail call void @fun() ret void } +;. +; CHECK: attributes #[[ATTR0]] = { norecurse } +;. diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion1.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion1.ll index af986eff3e13c..cd940379c5f53 100644 --- a/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion1.ll +++ b/llvm/test/Transforms/FunctionAttrs/norecurse_multi_scc_indirect_recursion1.ll @@ -1,4 +1,4 @@ -; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5 +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals all --version 5 ; RUN: opt < %s -passes=norecurse-lto-inference -S | FileCheck %s ; This test includes a call graph with multiple SCCs. The purpose of this is @@ -11,9 +11,9 @@ ; chain. but does not call back f1() and hence f1() can be marked as ; norecurse. -define dso_local noundef i32 @main() norecurse { +define dso_local i32 @main() norecurse { ; CHECK: Function Attrs: norecurse -; CHECK-LABEL: define dso_local noundef i32 @main( +; CHECK-LABEL: define dso_local i32 @main( ; CHECK-SAME: ) #[[ATTR0:[0-9]+]] { ; CHECK-NEXT: [[ENTRY:.*:]] ; CHECK-NEXT: tail call void @f1() @@ -93,3 +93,6 @@ entry: tail call void @fun() ret void } +;. +; CHECK: attributes #[[ATTR0]] = { norecurse } +;. diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll index 554642ff6963c..461e5dff92905 100644 --- a/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll +++ b/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll @@ -1,19 +1,23 @@ -; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5 +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals all --version 5 ; RUN: opt < %s -passes=norecurse-lto-inference -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. +; main -> bob -> callee1 -> callee2 +; where callee2 is self recursive. @x = dso_local global i32 4, align 4 @y = dso_local global i32 2, align 4 -define internal void @callee6() { +;. +; CHECK: @x = dso_local global i32 4, align 4 +; CHECK: @y = dso_local global i32 2, align 4 +;. +define internal void @callee2() { ; CHECK: Function Attrs: norecurse -; CHECK-LABEL: define internal void @callee6( +; CHECK-LABEL: define internal void @callee2( ; CHECK-SAME: ) #[[ATTR0:[0-9]+]] { ; CHECK-NEXT: [[ENTRY:.*:]] ; CHECK-NEXT: [[TMP0:%.*]] = load volatile i32, ptr @y, align 4 @@ -28,17 +32,17 @@ entry: ret void } -define internal void @callee5(i32 noundef %x) { -; CHECK-LABEL: define internal void @callee5( -; CHECK-SAME: i32 noundef [[X:%.*]]) { +define internal void @callee1(i32 %x) { +; CHECK-LABEL: define internal void @callee1( +; CHECK-SAME: i32 [[X:%.*]]) { ; 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: tail call void @callee1(i32 [[X]]) ; CHECK-NEXT: br label %[[IF_END]] ; CHECK: [[IF_END]]: -; CHECK-NEXT: tail call void @callee6() +; CHECK-NEXT: tail call void @callee2() ; CHECK-NEXT: ret void ; entry: @@ -46,84 +50,30 @@ entry: br i1 %cmp, label %if.then, label %if.end if.then: ; preds = %entry - tail call void @callee5(i32 noundef %x) + tail call void @callee1(i32 %x) br label %if.end if.end: ; preds = %if.then, %entry - tail call void @callee6() - ret void -} - -define internal void @callee4() { -; CHECK: Function Attrs: norecurse -; CHECK-LABEL: define internal void @callee4( -; CHECK-SAME: ) #[[ATTR0]] { -; 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: norecurse -; CHECK-LABEL: define internal void @callee3( -; CHECK-SAME: ) #[[ATTR0]] { -; 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: norecurse -; CHECK-LABEL: define internal void @callee2( -; CHECK-SAME: ) #[[ATTR0]] { -; 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: norecurse -; CHECK-LABEL: define internal void @callee1( -; CHECK-SAME: ) #[[ATTR0]] { -; 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: norecurse -; CHECK-LABEL: define internal void @bob( -; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-LABEL: define internal void @bob() { ; CHECK-NEXT: [[ENTRY:.*:]] -; CHECK-NEXT: tail call void @callee1() +; CHECK-NEXT: [[TMP0:%.*]] = load volatile i32, ptr @x, align 4 +; CHECK-NEXT: tail call void @callee2(i32 [[TMP0]]) ; CHECK-NEXT: ret void ; entry: - tail call void @callee1() + %0 = load volatile i32, ptr @x, align 4 + tail call void @callee2(i32 %0) ret void } -define dso_local noundef i32 @main() norecurse { +define dso_local i32 @main() norecurse { ; CHECK: Function Attrs: norecurse -; CHECK-LABEL: define dso_local noundef i32 @main( +; CHECK-LABEL: define dso_local i32 @main( ; CHECK-SAME: ) #[[ATTR0]] { ; CHECK-NEXT: [[ENTRY:.*:]] ; CHECK-NEXT: tail call void @bob() @@ -133,3 +83,6 @@ entry: tail call void @bob() ret i32 0 } +;. +; CHECK: attributes #[[ATTR0]] = { norecurse } +;. From d160bc8cb9b5801cfbde1ffd26c00f44f7231267 Mon Sep 17 00:00:00 2001 From: Usha Gupta Date: Mon, 29 Sep 2025 15:42:05 +0000 Subject: [PATCH 3/3] Add tests, clarify comments --- llvm/lib/Transforms/IPO/FunctionAttrs.cpp | 6 +- .../Transforms/FunctionAttrs/norecurse_lto.ll | 69 +++++++++++++++++++ .../norecurse_multinode_refscc.ll | 41 +++++++++++ 3 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 llvm/test/Transforms/FunctionAttrs/norecurse_lto.ll create mode 100644 llvm/test/Transforms/FunctionAttrs/norecurse_multinode_refscc.ll diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp index ccf2fafb4493b..50130da01c7ba 100644 --- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp +++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp @@ -2472,12 +2472,12 @@ PreservedAnalyses NoRecurseLTOInferencePass::run(Module &M, for (LazyCallGraph::RefSCC &RC : CG.postorder_ref_sccs()) { // Skip any RefSCC that is part of a call cycle. A RefSCC containing more - // than one SCC indicates a recursive relationship, which could involve - // direct or indirect calls. + // than one SCC indicates a recursive relationship involving indirect calls. if (RC.size() > 1) continue; - // A single-SCC RefSCC could still be a self-loop. + // RefSCC contains a single-SCC. SCC size > 1 indicates mutually recursive + // functions. Ex: foo1 -> foo2 -> foo3 -> foo1. LazyCallGraph::SCC &S = *RC.begin(); if (S.size() > 1) continue; diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_lto.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_lto.ll new file mode 100644 index 0000000000000..5be707bef8655 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/norecurse_lto.ll @@ -0,0 +1,69 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals all --version 5 +; RUN: opt < %s -passes=norecurse-lto-inference -S | FileCheck %s + +; This test includes a call graph which has a recursive function(foo2) which +; calls a non-recursive internal function (foo3) satisfying the norecurse +; attribute criteria. + + +define internal void @foo3() { +; CHECK: Function Attrs: norecurse +; CHECK-LABEL: define internal void @foo3( +; CHECK-SAME: ) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: ret void +; + ret void +} + +define internal i32 @foo2(i32 %accum, i32 %n) { +; CHECK-LABEL: define internal i32 @foo2( +; CHECK-SAME: i32 [[ACCUM:%.*]], i32 [[N:%.*]]) { +; CHECK-NEXT: [[ENTRY:.*]]: +; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[N]], 0 +; CHECK-NEXT: br i1 [[CMP]], label %[[EXIT:.*]], label %[[RECURSE:.*]] +; CHECK: [[RECURSE]]: +; CHECK-NEXT: [[SUB:%.*]] = sub i32 [[N]], 1 +; CHECK-NEXT: [[MUL:%.*]] = mul i32 [[ACCUM]], [[SUB]] +; CHECK-NEXT: [[CALL:%.*]] = call i32 @foo2(i32 [[MUL]], i32 [[SUB]]) +; CHECK-NEXT: call void @foo3() +; CHECK-NEXT: br label %[[EXIT]] +; CHECK: [[EXIT]]: +; CHECK-NEXT: [[RES:%.*]] = phi i32 [ [[ACCUM]], %[[ENTRY]] ], [ [[CALL]], %[[RECURSE]] ] +; CHECK-NEXT: ret i32 [[RES]] +; +entry: + %cmp = icmp eq i32 %n, 0 + br i1 %cmp, label %exit, label %recurse + +recurse: + %sub = sub i32 %n, 1 + %mul = mul i32 %accum, %sub + %call = call i32 @foo2(i32 %mul, i32 %sub) + call void @foo3() + br label %exit + +exit: + %res = phi i32 [ %accum, %entry ], [ %call, %recurse ] + ret i32 %res +} + +define internal i32 @foo1() { +; CHECK-LABEL: define internal i32 @foo1() { +; CHECK-NEXT: [[RES:%.*]] = call i32 @foo2(i32 1, i32 5) +; CHECK-NEXT: ret i32 [[RES]] +; + %res = call i32 @foo2(i32 1, i32 5) + ret i32 %res +} + +define dso_local i32 @main() { +; CHECK-LABEL: define dso_local i32 @main() { +; CHECK-NEXT: [[RES:%.*]] = call i32 @foo1() +; CHECK-NEXT: ret i32 [[RES]] +; + %res = call i32 @foo1() + ret i32 %res +} +;. +; CHECK: attributes #[[ATTR0]] = { norecurse } +;. diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_multinode_refscc.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_multinode_refscc.ll new file mode 100644 index 0000000000000..8b81a900bdef8 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/norecurse_multinode_refscc.ll @@ -0,0 +1,41 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals all --version 5 +; RUN: opt -passes=norecurse-lto-inference -S %s | FileCheck %s + +; This is a negative test which results in RefSCC with size > 1. +; RefSCC : [(f2), (f1)] +; --- SCC A (f1) --- size() = 1 +define internal void @f1() { +; CHECK-LABEL: define internal void @f1() { +; CHECK-NEXT: call void @f2() +; CHECK-NEXT: ret void +; + call void @f2() + ret void +} + +; --- SCC B (f2) --- size() = 1 +; f2 indirectly calls f1 using locally allocated function pointer +define internal void @f2() { +; CHECK-LABEL: define internal void @f2() { +; CHECK-NEXT: [[FP:%.*]] = alloca ptr, align 8 +; CHECK-NEXT: store ptr @f1, ptr [[FP]], align 8 +; CHECK-NEXT: [[TMP:%.*]] = load ptr, ptr [[FP]], align 8 +; CHECK-NEXT: call void [[TMP]]() +; CHECK-NEXT: ret void +; + %fp = alloca void ()* + store void ()* @f1, void ()** %fp + %tmp = load void ()*, void ()** %fp + call void %tmp() + ret void +} + +define i32 @main() { +; CHECK-LABEL: define i32 @main() { +; CHECK-NEXT: call void @f1() +; CHECK-NEXT: ret i32 0 +; + call void @f1() + ret i32 0 +} +