[llvm] [FuncAttrs] Relax norecurse attribute inference (PR #139943)

Usha Gupta via llvm-commits llvm-commits at lists.llvm.org
Fri Jun 13 02:58:45 PDT 2025


https://github.com/usha1830 updated https://github.com/llvm/llvm-project/pull/139943

>From 0e5eda5835db7adee4ab5636f77a8cf078c6f96b Mon Sep 17 00:00:00 2001
From: Usha Gupta <usha.gupta at arm.com>
Date: Wed, 14 May 2025 16:22:34 +0000
Subject: [PATCH 1/5] [FuncAttrs] Relax norecurse attribute when callee is
 self-recursive or is part of a recursive call chain which does not include
 the caller.

---
 llvm/lib/Transforms/IPO/FunctionAttrs.cpp     |  86 +++++++--
 .../norecurse_multiSCC_indirect_recursion.ll  | 159 ++++++++++++++++
 .../norecurse_multiSCC_indirect_recursion1.ll | 102 +++++++++++
 .../norecurse_self_recursive_callee.ll        | 173 ++++++++++++++++++
 4 files changed, 503 insertions(+), 17 deletions(-)
 create mode 100644 llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion.ll
 create mode 100644 llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion1.ll
 create mode 100644 llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll

diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index f918d7e059b63..293699a1639c6 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -2059,8 +2059,49 @@ static void inferAttrsFromFunctionBodies(const SCCNodeSet &SCCNodes,
   AI.run(SCCNodes, Changed);
 }
 
+/// Returns true if N or any function it (transitively) calls makes a call
+/// to an unknown external function: either an indirect call or a declaration
+/// without the NoCallback attribute.
+static bool callsUnknownExternal(LazyCallGraph::Node *N) {
+  std::deque<LazyCallGraph::Node *> Worklist;
+  DenseSet<LazyCallGraph::Node *> Visited;
+  Worklist.push_back(N);
+  Visited.insert(N);
+
+  while (!Worklist.empty()) {
+    auto *Cur = Worklist.front();
+    Worklist.pop_front();
+
+    Function &F = Cur->getFunction();
+    for (auto &BB : F) {
+      for (auto &I : BB.instructionsWithoutDebug()) {
+        if (auto *CB = dyn_cast<CallBase>(&I)) {
+          const Function *Callee = CB->getCalledFunction();
+          // Indirect call, treat as unknown external function.
+          if (!Callee)
+            return true;
+          // Extern declaration without NoCallback attribute
+          if (Callee->isDeclaration() &&
+              !Callee->hasFnAttribute(Attribute::NoCallback))
+            return true;
+        }
+      }
+    }
+
+    // Enqueue all direct call-edge successors for further scanning
+    for (auto &Edge : Cur->populate().calls()) {
+      LazyCallGraph::Node *Succ = &Edge.getNode();
+      if (Visited.insert(Succ).second)
+        Worklist.push_back(Succ);
+    }
+  }
+
+  return false;
+}
+
 static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
-                              SmallSet<Function *, 8> &Changed) {
+                              SmallSet<Function *, 8> &Changed,
+                              LazyCallGraph &CG) {
   // Try and identify functions that do not recurse.
 
   // If the SCC contains multiple nodes we know for sure there is recursion.
@@ -2071,24 +2112,35 @@ 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<CallBase>(&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;
+
+        // External call with NoCallback attribute.
+        if (Callee->isDeclaration()) {
+          if (Callee->hasFnAttribute(Attribute::NoCallback))
+            continue;
           return;
+        }
+
+        if (auto *CNode = CG.lookup(*Callee))
+          if (callsUnknownExternal(CNode))
+            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 and we have no
+  // indirect recursion as the SCC size is one. This function cannot recurse.
   F->setDoesNotRecurse();
   ++NumNoRecurse;
   Changed.insert(F);
@@ -2248,7 +2300,7 @@ static SCCNodesResult createSCCNodeSet(ArrayRef<Function *> Functions) {
 template <typename AARGetterT>
 static SmallSet<Function *, 8>
 deriveAttrsInPostOrder(ArrayRef<Function *> Functions, AARGetterT &&AARGetter,
-                       bool ArgAttrsOnly) {
+                       bool ArgAttrsOnly, LazyCallGraph &CG) {
   SCCNodesResult Nodes = createSCCNodeSet(Functions);
 
   // Bail if the SCC only contains optnone functions.
@@ -2279,7 +2331,7 @@ deriveAttrsInPostOrder(ArrayRef<Function *> Functions, AARGetterT &&AARGetter,
     addNoAliasAttrs(Nodes.SCCNodes, Changed);
     addNonNullAttrs(Nodes.SCCNodes, Changed);
     inferAttrsFromFunctionBodies(Nodes.SCCNodes, Changed);
-    addNoRecurseAttrs(Nodes.SCCNodes, Changed);
+    addNoRecurseAttrs(Nodes.SCCNodes, Changed, CG);
   }
 
   // Finally, infer the maximal set of attributes from the ones we've inferred
@@ -2323,7 +2375,7 @@ PreservedAnalyses PostOrderFunctionAttrsPass::run(LazyCallGraph::SCC &C,
   }
 
   auto ChangedFunctions =
-      deriveAttrsInPostOrder(Functions, AARGetter, ArgAttrsOnly);
+      deriveAttrsInPostOrder(Functions, AARGetter, ArgAttrsOnly, CG);
   if (ChangedFunctions.empty())
     return PreservedAnalyses::all();
 
diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion.ll
new file mode 100644
index 0000000000000..c0af16b069bde
--- /dev/null
+++ b/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion.ll
@@ -0,0 +1,159 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5
+; RUN: opt < %s -passes=function-attrs -S | FileCheck %s
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+; 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:  main, 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 dso_local void @bar1() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local void @bar1(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CALL:%.*]] = tail call i32 @main()
+; CHECK-NEXT:    ret void
+;
+entry:
+  %call = tail call i32 @main()
+  ret void
+}
+
+; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+define dso_local noundef i32 @main() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local noundef i32 @main(
+; CHECK-SAME: ) local_unnamed_addr #[[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 i32 0
+;
+entry:
+  tail call void @foo()
+  tail call void @bar2()
+  tail call void @baz()
+  ret i32 0
+}
+
+; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+define dso_local void @foo1() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local void @foo1(
+; CHECK-SAME: ) local_unnamed_addr #[[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 dso_local void @bar() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local void @bar(
+; CHECK-SAME: ) local_unnamed_addr #[[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 dso_local void @foo() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local void @foo(
+; CHECK-SAME: ) local_unnamed_addr #[[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 dso_local void @bar4() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local void @bar4(
+; CHECK-SAME: ) local_unnamed_addr #[[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 dso_local void @bar2() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local void @bar2(
+; CHECK-SAME: ) local_unnamed_addr #[[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 dso_local void @bar3() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local void @bar3(
+; CHECK-SAME: ) local_unnamed_addr #[[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 dso_local void @fun() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local void @fun(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
+; 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 dso_local void @baz() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local void @baz(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    tail call void @fun()
+; CHECK-NEXT:    ret void
+;
+entry:
+  tail call void @fun()
+  ret void
+}
+
+attributes #0 = { nofree noinline nosync nounwind memory(none) uwtable "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fp-armv8,+neon,+outline-atomics,+v8a,-fmv" }
diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion1.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion1.ll
new file mode 100644
index 0000000000000..60bbaab2a7d66
--- /dev/null
+++ b/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion1.ll
@@ -0,0 +1,102 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5
+; RUN: opt < %s -passes=function-attrs -S | FileCheck %s
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+; 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
+; main() calls bar2 and baz, both of which are part of some indirect recursive
+; chain. but does not call back main() and hence main() can be marked as 
+; norecurse.
+
+; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+define dso_local noundef i32 @main() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline norecurse nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local noundef i32 @main(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    tail call void @bar2()
+; CHECK-NEXT:    tail call void @baz()
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  tail call void @bar2()
+  tail call void @baz()
+  ret i32 0
+}
+
+; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+define dso_local void @bar4() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local void @bar4(
+; CHECK-SAME: ) local_unnamed_addr #[[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 dso_local void @bar2() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local void @bar2(
+; CHECK-SAME: ) local_unnamed_addr #[[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 dso_local void @bar3() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local void @bar3(
+; CHECK-SAME: ) local_unnamed_addr #[[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 dso_local void @fun() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local void @fun(
+; CHECK-SAME: ) local_unnamed_addr #[[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 dso_local void @baz() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define dso_local void @baz(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR1]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    tail call void @fun()
+; CHECK-NEXT:    ret void
+;
+entry:
+  tail call void @fun()
+  ret void
+}
+
+attributes #0 = { nofree noinline nosync nounwind memory(none) uwtable "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fp-armv8,+neon,+outline-atomics,+v8a,-fmv" }
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..9b87c4e4ad76c
--- /dev/null
+++ b/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll
@@ -0,0 +1,173 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5
+; RUN: opt < %s -passes=function-attrs -S | FileCheck %s
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+; 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.
+
+ at x = dso_local global i32 4, align 4
+ at y = dso_local global i32 2, align 4
+
+; Function Attrs: nofree noinline norecurse nounwind memory(readwrite, argmem: none) uwtable
+define dso_local void @callee6() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline norecurse nounwind memory(readwrite, argmem: none) uwtable
+; CHECK-LABEL: define dso_local void @callee6(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = load volatile i32, ptr @y, align 4, !tbaa [[TBAA8:![0-9]+]]
+; CHECK-NEXT:    [[INC:%.*]] = add nsw i32 [[TMP0]], 1
+; CHECK-NEXT:    store volatile i32 [[INC]], ptr @y, align 4, !tbaa [[TBAA8]]
+; CHECK-NEXT:    ret void
+;
+entry:
+  %0 = load volatile i32, ptr @y, align 4, !tbaa !8
+  %inc = add nsw i32 %0, 1
+  store volatile i32 %inc, ptr @y, align 4, !tbaa !8
+  ret void
+}
+
+; Function Attrs: nofree noinline nounwind uwtable
+define dso_local void @callee5(i32 noundef %x) local_unnamed_addr #1 {
+; CHECK: Function Attrs: nofree noinline nounwind uwtable
+; CHECK-LABEL: define dso_local void @callee5(
+; CHECK-SAME: i32 noundef [[X:%.*]]) local_unnamed_addr #[[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
+}
+
+; Function Attrs: nofree noinline nounwind uwtable
+define dso_local void @callee4() local_unnamed_addr #1 {
+; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
+; CHECK-LABEL: define dso_local void @callee4(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR2:[0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = load volatile i32, ptr @x, align 4, !tbaa [[TBAA8]]
+; CHECK-NEXT:    tail call void @callee5(i32 noundef [[TMP0]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  %0 = load volatile i32, ptr @x, align 4, !tbaa !8
+  tail call void @callee5(i32 noundef %0)
+  ret void
+}
+
+; Function Attrs: nofree noinline nounwind uwtable
+define dso_local void @callee3() local_unnamed_addr #1 {
+; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
+; CHECK-LABEL: define dso_local void @callee3(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR2]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    tail call void @callee4()
+; CHECK-NEXT:    ret void
+;
+entry:
+  tail call void @callee4()
+  ret void
+}
+
+; Function Attrs: nofree noinline nounwind uwtable
+define dso_local void @callee2() local_unnamed_addr #1 {
+; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
+; CHECK-LABEL: define dso_local void @callee2(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR2]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    tail call void @callee3()
+; CHECK-NEXT:    ret void
+;
+entry:
+  tail call void @callee3()
+  ret void
+}
+
+; Function Attrs: nofree noinline nounwind uwtable
+define dso_local void @callee1() local_unnamed_addr #1 {
+; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
+; CHECK-LABEL: define dso_local void @callee1(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR2]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    tail call void @callee2()
+; CHECK-NEXT:    ret void
+;
+entry:
+  tail call void @callee2()
+  ret void
+}
+
+; Function Attrs: nofree noinline nounwind uwtable
+define dso_local void @bob() local_unnamed_addr #1 {
+; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
+; CHECK-LABEL: define dso_local void @bob(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR2]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    tail call void @callee1()
+; CHECK-NEXT:    ret void
+;
+entry:
+  tail call void @callee1()
+  ret void
+}
+
+; Function Attrs: nofree nounwind uwtable
+define dso_local noundef i32 @main() local_unnamed_addr #2 {
+; CHECK: Function Attrs: nofree norecurse nounwind uwtable
+; CHECK-LABEL: define dso_local noundef i32 @main(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR3:[0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    tail call void @bob()
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  tail call void @bob()
+  ret i32 0
+}
+
+attributes #0 = { nofree noinline norecurse nounwind memory(readwrite, argmem: none) uwtable "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fp-armv8,+neon,+outline-atomics,+v8a,-fmv" }
+attributes #1 = { nofree noinline nounwind uwtable "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fp-armv8,+neon,+outline-atomics,+v8a,-fmv" }
+attributes #2 = { nofree nounwind uwtable "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fp-armv8,+neon,+outline-atomics,+v8a,-fmv" }
+
+!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6}
+!llvm.ident = !{!7}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 8, !"PIC Level", i32 2}
+!2 = !{i32 7, !"PIE Level", i32 2}
+!3 = !{i32 7, !"uwtable", i32 2}
+!4 = !{i32 7, !"frame-pointer", i32 1}
+!5 = !{i32 1, !"ThinLTO", i32 0}
+!6 = !{i32 1, !"EnableSplitLTOUnit", i32 1}
+!7 = !{!"clang version 21.0.0git (https://github.com/llvm/llvm-project db42345dc660329e34fd119fc8edab74521f7c06)"}
+!8 = !{!9, !9, i64 0}
+!9 = !{!"int", !10, i64 0}
+!10 = !{!"omnipotent char", !11, i64 0}
+!11 = !{!"Simple C/C++ TBAA"}
+
+;.
+; CHECK: [[TBAA8]] = !{[[META9:![0-9]+]], [[META9]], i64 0}
+; CHECK: [[META9]] = !{!"int", [[META10:![0-9]+]], i64 0}
+; CHECK: [[META10]] = !{!"omnipotent char", [[META11:![0-9]+]], i64 0}
+; CHECK: [[META11]] = !{!"Simple C/C++ TBAA"}
+;.

>From 828e7773be9f48db91d4cd4e16d3e65e7a078b94 Mon Sep 17 00:00:00 2001
From: Usha Gupta <usha.gupta at arm.com>
Date: Tue, 20 May 2025 16:26:07 +0000
Subject: [PATCH 2/5] Skip checks on callee which already have norecurse
 attribute

---
 llvm/lib/Transforms/IPO/FunctionAttrs.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index 293699a1639c6..f51e22d7c6264 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -2124,6 +2124,9 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
         if (!Callee || Callee == F)
           return;
 
+        if (Callee->doesNotRecurse())
+          continue;
+
         // External call with NoCallback attribute.
         if (Callee->isDeclaration()) {
           if (Callee->hasFnAttribute(Attribute::NoCallback))

>From b850dc1f9cbd085dfd1e453487f2621c140261ac Mon Sep 17 00:00:00 2001
From: Usha Gupta <usha.gupta at arm.com>
Date: Thu, 5 Jun 2025 17:13:38 +0000
Subject: [PATCH 3/5] Bail without adding norecurse if address of function is
 taken as it could be called back in

---
 llvm/lib/Transforms/IPO/FunctionAttrs.cpp     |  55 +---
 .../AMDGPU/amdgpu-simplify-libcall-sincos.ll  |   8 +-
 .../Other/cgscc-iterate-function-mutation.ll  |  40 +--
 .../FunctionAttrs/nofree-attributor.ll        |   4 +-
 llvm/test/Transforms/FunctionAttrs/nonnull.ll |   2 +-
 .../test/Transforms/FunctionAttrs/noreturn.ll |   8 +-
 .../test/Transforms/FunctionAttrs/nounwind.ll | 284 ++++++++++++------
 .../FunctionAttrs/sendmsg-nocallback.ll       |   9 +-
 llvm/test/Transforms/Inline/cgscc-update.ll   |  20 +-
 llvm/test/Transforms/PhaseOrdering/pr95152.ll |   6 +-
 10 files changed, 247 insertions(+), 189 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index f51e22d7c6264..d08a2df9d9e66 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -2059,49 +2059,8 @@ static void inferAttrsFromFunctionBodies(const SCCNodeSet &SCCNodes,
   AI.run(SCCNodes, Changed);
 }
 
-/// Returns true if N or any function it (transitively) calls makes a call
-/// to an unknown external function: either an indirect call or a declaration
-/// without the NoCallback attribute.
-static bool callsUnknownExternal(LazyCallGraph::Node *N) {
-  std::deque<LazyCallGraph::Node *> Worklist;
-  DenseSet<LazyCallGraph::Node *> Visited;
-  Worklist.push_back(N);
-  Visited.insert(N);
-
-  while (!Worklist.empty()) {
-    auto *Cur = Worklist.front();
-    Worklist.pop_front();
-
-    Function &F = Cur->getFunction();
-    for (auto &BB : F) {
-      for (auto &I : BB.instructionsWithoutDebug()) {
-        if (auto *CB = dyn_cast<CallBase>(&I)) {
-          const Function *Callee = CB->getCalledFunction();
-          // Indirect call, treat as unknown external function.
-          if (!Callee)
-            return true;
-          // Extern declaration without NoCallback attribute
-          if (Callee->isDeclaration() &&
-              !Callee->hasFnAttribute(Attribute::NoCallback))
-            return true;
-        }
-      }
-    }
-
-    // Enqueue all direct call-edge successors for further scanning
-    for (auto &Edge : Cur->populate().calls()) {
-      LazyCallGraph::Node *Succ = &Edge.getNode();
-      if (Visited.insert(Succ).second)
-        Worklist.push_back(Succ);
-    }
-  }
-
-  return false;
-}
-
 static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
-                              SmallSet<Function *, 8> &Changed,
-                              LazyCallGraph &CG) {
+                              SmallSet<Function *, 8> &Changed) {
   // Try and identify functions that do not recurse.
 
   // If the SCC contains multiple nodes we know for sure there is recursion.
@@ -2112,6 +2071,8 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
   if (!F || !F->hasExactDefinition() || F->doesNotRecurse())
     return;
 
+  if (F->hasAddressTaken())
+    return;
   // 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
@@ -2133,10 +2094,6 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
             continue;
           return;
         }
-
-        if (auto *CNode = CG.lookup(*Callee))
-          if (callsUnknownExternal(CNode))
-            return;
       }
     }
   }
@@ -2303,7 +2260,7 @@ static SCCNodesResult createSCCNodeSet(ArrayRef<Function *> Functions) {
 template <typename AARGetterT>
 static SmallSet<Function *, 8>
 deriveAttrsInPostOrder(ArrayRef<Function *> Functions, AARGetterT &&AARGetter,
-                       bool ArgAttrsOnly, LazyCallGraph &CG) {
+                       bool ArgAttrsOnly) {
   SCCNodesResult Nodes = createSCCNodeSet(Functions);
 
   // Bail if the SCC only contains optnone functions.
@@ -2334,7 +2291,7 @@ deriveAttrsInPostOrder(ArrayRef<Function *> Functions, AARGetterT &&AARGetter,
     addNoAliasAttrs(Nodes.SCCNodes, Changed);
     addNonNullAttrs(Nodes.SCCNodes, Changed);
     inferAttrsFromFunctionBodies(Nodes.SCCNodes, Changed);
-    addNoRecurseAttrs(Nodes.SCCNodes, Changed, CG);
+    addNoRecurseAttrs(Nodes.SCCNodes, Changed);
   }
 
   // Finally, infer the maximal set of attributes from the ones we've inferred
@@ -2378,7 +2335,7 @@ PreservedAnalyses PostOrderFunctionAttrsPass::run(LazyCallGraph::SCC &C,
   }
 
   auto ChangedFunctions =
-      deriveAttrsInPostOrder(Functions, AARGetter, ArgAttrsOnly, CG);
+      deriveAttrsInPostOrder(Functions, AARGetter, ArgAttrsOnly);
   if (ChangedFunctions.empty())
     return PreservedAnalyses::all();
 
diff --git a/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-sincos.ll b/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-sincos.ll
index 34777eff0e856..6e2b825a45f56 100644
--- a/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-sincos.ll
+++ b/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-sincos.ll
@@ -725,7 +725,7 @@ entry:
 
 define void @sincos_f32_value_is_different_constexpr(ptr addrspace(1) nocapture writeonly %sin_out, ptr addrspace(1) nocapture writeonly %cos_out) {
 ; CHECK-LABEL: define void @sincos_f32_value_is_different_constexpr
-; CHECK-SAME: (ptr addrspace(1) writeonly captures(none) initializes((0, 4)) [[SIN_OUT:%.*]], ptr addrspace(1) writeonly captures(none) initializes((0, 4)) [[COS_OUT:%.*]]) #[[ATTR2]] {
+; CHECK-SAME: (ptr addrspace(1) writeonly captures(none) initializes((0, 4)) [[SIN_OUT:%.*]], ptr addrspace(1) writeonly captures(none) initializes((0, 4)) [[COS_OUT:%.*]]) #[[ATTR6:[0-9]+]] {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[CALL:%.*]] = tail call contract float @_Z3sinf(float bitcast (i32 ptrtoint (ptr @func to i32) to float))
 ; CHECK-NEXT:    store float [[CALL]], ptr addrspace(1) [[SIN_OUT]], align 4
@@ -881,7 +881,7 @@ entry:
 
 define float @sincos_f32_unused_result_cos(float %x) {
 ; CHECK-LABEL: define float @sincos_f32_unused_result_cos
-; CHECK-SAME: (float [[X:%.*]]) local_unnamed_addr #[[ATTR6:[0-9]+]] {
+; CHECK-SAME: (float [[X:%.*]]) local_unnamed_addr #[[ATTR7:[0-9]+]] {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[SIN:%.*]] = tail call contract float @_Z3sinf(float [[X]])
 ; CHECK-NEXT:    ret float [[SIN]]
@@ -896,7 +896,7 @@ entry:
 
 define float @sincos_f32_unused_result_sin(float %x) {
 ; CHECK-LABEL: define float @sincos_f32_unused_result_sin
-; CHECK-SAME: (float [[X:%.*]]) local_unnamed_addr #[[ATTR6]] {
+; CHECK-SAME: (float [[X:%.*]]) local_unnamed_addr #[[ATTR7]] {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[COS:%.*]] = tail call contract float @_Z3cosf(float [[X]])
 ; CHECK-NEXT:    ret float [[COS]]
@@ -911,7 +911,7 @@ entry:
 
 define void @sincos_f32_repeated_uses(float %x, ptr addrspace(1) %sin_out, ptr addrspace(1) %cos_out) {
 ; CHECK-LABEL: define void @sincos_f32_repeated_uses
-; CHECK-SAME: (float [[X:%.*]], ptr addrspace(1) [[SIN_OUT:%.*]], ptr addrspace(1) [[COS_OUT:%.*]]) local_unnamed_addr #[[ATTR7:[0-9]+]] {
+; CHECK-SAME: (float [[X:%.*]], ptr addrspace(1) [[SIN_OUT:%.*]], ptr addrspace(1) [[COS_OUT:%.*]]) local_unnamed_addr #[[ATTR8:[0-9]+]] {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[__SINCOS_:%.*]] = alloca float, align 4, addrspace(5)
 ; CHECK-NEXT:    [[TMP0:%.*]] = call contract float @_Z6sincosfPU3AS5f(float [[X]], ptr addrspace(5) [[__SINCOS_]])
diff --git a/llvm/test/Other/cgscc-iterate-function-mutation.ll b/llvm/test/Other/cgscc-iterate-function-mutation.ll
index aafd38d1e8825..631674362bb09 100644
--- a/llvm/test/Other/cgscc-iterate-function-mutation.ll
+++ b/llvm/test/Other/cgscc-iterate-function-mutation.ll
@@ -9,7 +9,7 @@ declare void @reference_function_pointer(ptr) nofree nosync readnone
 ; and the RefSCCs that those functions are in, we re-run the CGSCC passes to
 ; observe the refined call graph structure.
 
-; CHECK: define void @test1_a() {
+; CHECK: define void @test1_a() #1 {
 define void @test1_a() {
   call void @test1_b1()
   call void @test1_b2()
@@ -128,7 +128,7 @@ exit:
 ; multiple layers that have to be traversed in the correct order instead of
 ; a single node.
 
-; CHECK: define void @test3_a() {
+; CHECK: define void @test3_a() #1 {
 define void @test3_a() {
   call void @test3_b11()
   call void @test3_b21()
@@ -137,13 +137,13 @@ define void @test3_a() {
   ret void
 }
 
-; CHECK: define void @test3_b11() #0 {
+; CHECK: define void @test3_b11() #2 {
 define void @test3_b11() {
   call void @test3_b12()
   ret void
 }
 
-; CHECK: define void @test3_b12() #0 {
+; CHECK: define void @test3_b12() #2 {
 define void @test3_b12() {
   call void @test3_b13()
   ret void
@@ -155,13 +155,13 @@ define void @test3_b13() {
   ret void
 }
 
-; CHECK: define void @test3_b21() #0 {
+; CHECK: define void @test3_b21() #2 {
 define void @test3_b21() {
   call void @test3_b22()
   ret void
 }
 
-; CHECK: define void @test3_b22() #0 {
+; CHECK: define void @test3_b22() #2 {
 define void @test3_b22() {
   call void @test3_b23()
   ret void
@@ -180,13 +180,13 @@ exit:
   ret void
 }
 
-; CHECK: define void @test3_b31() {
+; CHECK: define void @test3_b31() #1 {
 define void @test3_b31() {
   call void @test3_b32()
   ret void
 }
 
-; CHECK: define void @test3_b32() {
+; CHECK: define void @test3_b32() #1 {
 define void @test3_b32() {
   call void @test3_b33()
   ret void
@@ -205,13 +205,13 @@ exit:
   ret void
 }
 
-; CHECK: define void @test3_b41() #0 {
+; CHECK: define void @test3_b41() #2 {
 define void @test3_b41() {
   call void @test3_b42()
   ret void
 }
 
-; CHECK: define void @test3_b42() #0 {
+; CHECK: define void @test3_b42() #2 {
 define void @test3_b42() {
   call void @test3_b43()
   ret void
@@ -244,13 +244,13 @@ define void @test4_a() {
   ret void
 }
 
-; CHECK: define void @test4_b11() #0 {
+; CHECK: define void @test4_b11() #2 {
 define void @test4_b11() {
   call void @test4_b12()
   ret void
 }
 
-; CHECK: define void @test4_b12() #0 {
+; CHECK: define void @test4_b12() #2 {
 define void @test4_b12() {
   call void @test4_b13()
   ret void
@@ -262,20 +262,20 @@ define void @test4_b13() {
   ret void
 }
 
-; CHECK: define void @test4_b21() #0 {
+; CHECK: define void @test4_b21() #2 {
 define void @test4_b21() {
   call void @test4_b22()
   ret void
 }
 
-; CHECK: define void @test4_b22() #0 {
+; CHECK: define void @test4_b22() #2 {
 define void @test4_b22() {
   call void @test4_b23()
   ret void
 }
 
 ; CHECK: define void @test4_b23() #0 {
-define void @test4_b23() {
+define void @test4_b23() #0 {
   call void @reference_function_pointer(ptr @test4_a)
   br i1 false, label %dead, label %exit
 
@@ -287,13 +287,13 @@ exit:
   ret void
 }
 
-; CHECK: define void @test4_b31() {
+; CHECK: define void @test4_b31() #1 {
 define void @test4_b31() {
   call void @test4_b32()
   ret void
 }
 
-; CHECK: define void @test4_b32() {
+; CHECK: define void @test4_b32() #1 {
 define void @test4_b32() {
   call void @test4_b33()
   ret void
@@ -313,13 +313,13 @@ exit:
   ret void
 }
 
-; CHECK: define void @test4_b41() #0 {
+; CHECK: define void @test4_b41() #2 {
 define void @test4_b41() {
   call void @test4_b42()
   ret void
 }
 
-; CHECK: define void @test4_b42() #0 {
+; CHECK: define void @test4_b42() #2 {
 define void @test4_b42() {
   call void @test4_b43()
   ret void
@@ -339,3 +339,5 @@ exit:
 }
 
 ; CHECK: attributes #0 = { nofree nosync memory(none) }
+; CHECK: attributes #1 = { norecurse }
+; CHECK: attributes #2 = { nofree norecurse nosync memory(none) }
diff --git a/llvm/test/Transforms/FunctionAttrs/nofree-attributor.ll b/llvm/test/Transforms/FunctionAttrs/nofree-attributor.ll
index 5a0b6ef1e3043..1457e96e1af68 100644
--- a/llvm/test/Transforms/FunctionAttrs/nofree-attributor.ll
+++ b/llvm/test/Transforms/FunctionAttrs/nofree-attributor.ll
@@ -250,9 +250,9 @@ define void @f1() #0 {
 }
 
 define void @f2() #0 {
-; FNATTR: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+; FNATTR: Function Attrs: nofree noinline norecurse nosync nounwind memory(none) uwtable
 ; FNATTR-LABEL: define {{[^@]+}}@f2
-; FNATTR-SAME: () #[[ATTR4]] {
+; FNATTR-SAME: () #[[ATTR7:[0-9]+]] {
 ; FNATTR-NEXT:    tail call void @f1()
 ; FNATTR-NEXT:    ret void
 ;
diff --git a/llvm/test/Transforms/FunctionAttrs/nonnull.ll b/llvm/test/Transforms/FunctionAttrs/nonnull.ll
index 483b560ece6c8..b484210d75957 100644
--- a/llvm/test/Transforms/FunctionAttrs/nonnull.ll
+++ b/llvm/test/Transforms/FunctionAttrs/nonnull.ll
@@ -1105,7 +1105,7 @@ define internal void @optnone(ptr dereferenceable(4) %a) optnone noinline {
 }
 define void @make_live(ptr nonnull dereferenceable(8) %a) {
 ; FNATTRS-LABEL: define void @make_live(
-; FNATTRS-SAME: ptr nonnull dereferenceable(8) [[A:%.*]]) {
+; FNATTRS-SAME: ptr nonnull dereferenceable(8) [[A:%.*]]) #[[ATTR13:[0-9]+]] {
 ; FNATTRS-NEXT:    call void @naked(ptr nonnull align 16 dereferenceable(8) [[A]])
 ; FNATTRS-NEXT:    call void @control(ptr nonnull align 16 dereferenceable(8) [[A]])
 ; FNATTRS-NEXT:    call void @optnone(ptr nonnull align 16 dereferenceable(8) [[A]])
diff --git a/llvm/test/Transforms/FunctionAttrs/noreturn.ll b/llvm/test/Transforms/FunctionAttrs/noreturn.ll
index fa80f6c2eced4..965467ba6015f 100644
--- a/llvm/test/Transforms/FunctionAttrs/noreturn.ll
+++ b/llvm/test/Transforms/FunctionAttrs/noreturn.ll
@@ -2,25 +2,25 @@
 
 declare i32 @f()
 
-; CHECK: Function Attrs: noreturn
+; CHECK: Function Attrs: {{.*}}noreturn
 ; CHECK-NEXT: @noreturn()
 declare i32 @noreturn() noreturn
 
-; CHECK: Function Attrs: noreturn
+; CHECK: Function Attrs: {{.*}}noreturn
 ; CHECK-NEXT: @caller()
 define i32 @caller() {
   %c = call i32 @noreturn()
   ret i32 %c
 }
 
-; CHECK: Function Attrs: noreturn
+; CHECK: Function Attrs: {{.*}}noreturn
 ; CHECK-NEXT: @caller2()
 define i32 @caller2() {
   %c = call i32 @caller()
   ret i32 %c
 }
 
-; CHECK: Function Attrs: noreturn
+; CHECK: Function Attrs: {{.*}}noreturn
 ; CHECK-NEXT: @caller3()
 define i32 @caller3() {
 entry:
diff --git a/llvm/test/Transforms/FunctionAttrs/nounwind.ll b/llvm/test/Transforms/FunctionAttrs/nounwind.ll
index afa9ae31b5fba..b613c47435a03 100644
--- a/llvm/test/Transforms/FunctionAttrs/nounwind.ll
+++ b/llvm/test/Transforms/FunctionAttrs/nounwind.ll
@@ -4,10 +4,15 @@
 
 ; TEST 1
 define i32 @foo1() {
-; 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
+; 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
 ;
   ret i32 1
 }
@@ -70,14 +75,23 @@ define void @call_non_nounwind(){
 ; }
 
 define i32 @maybe_throw(i1 zeroext %0) {
-; 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
+; 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
 ;
   br i1 %0, label %2, label %3
 
@@ -101,18 +115,31 @@ declare void @__cxa_rethrow()
 ; }
 
 define i32 @catch_thing() personality ptr @__gxx_personality_v0 {
-; 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
+; 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
 ;
   invoke void @__cxa_rethrow() #1
   to label %1 unwind label %2
@@ -130,9 +157,15 @@ define i32 @catch_thing() personality ptr @__gxx_personality_v0 {
 }
 
 define i32 @catch_thing_user() {
-; COMMON-LABEL: define {{[^@]+}}@catch_thing_user() {
-; COMMON-NEXT:    [[CATCH_THING_CALL:%.*]] = call i32 @catch_thing()
-; COMMON-NEXT:    ret i32 [[CATCH_THING_CALL]]
+; FNATTRS: Function Attrs: norecurse
+; FNATTRS-LABEL: define {{[^@]+}}@catch_thing_user
+; FNATTRS-SAME: () #[[ATTR2:[0-9]+]] {
+; 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]]
 ;
   %catch_thing_call = call i32 @catch_thing()
   ret i32 %catch_thing_call
@@ -143,18 +176,31 @@ declare void @abort() nounwind
 @catch_ty = external global ptr
 
 define void @catch_specific_landingpad() personality ptr @__gxx_personality_v0 {
-; COMMON: Function Attrs: noreturn
-; 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:       lpad:
-; COMMON-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
-; COMMON-NEXT:    catch ptr @catch_ty
-; COMMON-NEXT:    call void @abort()
-; COMMON-NEXT:    unreachable
-; COMMON:       unreachable:
-; COMMON-NEXT:    unreachable
+; FNATTRS: Function Attrs: noreturn
+; FNATTRS-LABEL: define {{[^@]+}}@catch_specific_landingpad
+; FNATTRS-SAME: () #[[ATTR4:[0-9]+]] personality ptr @__gxx_personality_v0 {
+; FNATTRS-NEXT:    invoke void @do_throw()
+; FNATTRS-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
+; FNATTRS:       lpad:
+; FNATTRS-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
+; FNATTRS-NEXT:            catch ptr @catch_ty
+; FNATTRS-NEXT:    call void @abort()
+; FNATTRS-NEXT:    unreachable
+; FNATTRS:       unreachable:
+; FNATTRS-NEXT:    unreachable
+;
+; ATTRIBUTOR: Function Attrs: noreturn
+; ATTRIBUTOR-LABEL: define {{[^@]+}}@catch_specific_landingpad
+; ATTRIBUTOR-SAME: () #[[ATTR3:[0-9]+]] personality ptr @__gxx_personality_v0 {
+; ATTRIBUTOR-NEXT:    invoke void @do_throw()
+; ATTRIBUTOR-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
+; ATTRIBUTOR:       lpad:
+; ATTRIBUTOR-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
+; ATTRIBUTOR-NEXT:            catch ptr @catch_ty
+; ATTRIBUTOR-NEXT:    call void @abort()
+; ATTRIBUTOR-NEXT:    unreachable
+; ATTRIBUTOR:       unreachable:
+; ATTRIBUTOR-NEXT:    unreachable
 ;
   invoke void @do_throw()
   to label %unreachable unwind label %lpad
@@ -170,18 +216,31 @@ unreachable:
 }
 
 define void @catch_all_landingpad() personality ptr @__gxx_personality_v0 {
-; COMMON: Function Attrs: noreturn nounwind
-; 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:       lpad:
-; COMMON-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
-; COMMON-NEXT:    catch ptr null
-; COMMON-NEXT:    call void @abort()
-; COMMON-NEXT:    unreachable
-; COMMON:       unreachable:
-; COMMON-NEXT:    unreachable
+; FNATTRS: Function Attrs: noreturn nounwind
+; FNATTRS-LABEL: define {{[^@]+}}@catch_all_landingpad
+; FNATTRS-SAME: () #[[ATTR5:[0-9]+]] personality ptr @__gxx_personality_v0 {
+; FNATTRS-NEXT:    invoke void @do_throw()
+; FNATTRS-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
+; FNATTRS:       lpad:
+; FNATTRS-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
+; FNATTRS-NEXT:            catch ptr null
+; FNATTRS-NEXT:    call void @abort()
+; FNATTRS-NEXT:    unreachable
+; FNATTRS:       unreachable:
+; FNATTRS-NEXT:    unreachable
+;
+; ATTRIBUTOR: Function Attrs: noreturn nounwind
+; ATTRIBUTOR-LABEL: define {{[^@]+}}@catch_all_landingpad
+; ATTRIBUTOR-SAME: () #[[ATTR4:[0-9]+]] personality ptr @__gxx_personality_v0 {
+; ATTRIBUTOR-NEXT:    invoke void @do_throw()
+; ATTRIBUTOR-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
+; ATTRIBUTOR:       lpad:
+; ATTRIBUTOR-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
+; ATTRIBUTOR-NEXT:            catch ptr null
+; ATTRIBUTOR-NEXT:    call void @abort()
+; ATTRIBUTOR-NEXT:    unreachable
+; ATTRIBUTOR:       unreachable:
+; ATTRIBUTOR-NEXT:    unreachable
 ;
   invoke void @do_throw()
   to label %unreachable unwind label %lpad
@@ -197,18 +256,31 @@ unreachable:
 }
 
 define void @filter_specific_landingpad() personality ptr @__gxx_personality_v0 {
-; COMMON: Function Attrs: noreturn
-; 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:       lpad:
-; COMMON-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
-; COMMON-NEXT:    filter [1 x ptr] [ptr @catch_ty]
-; COMMON-NEXT:    call void @abort()
-; COMMON-NEXT:    unreachable
-; COMMON:       unreachable:
-; COMMON-NEXT:    unreachable
+; FNATTRS: Function Attrs: noreturn
+; FNATTRS-LABEL: define {{[^@]+}}@filter_specific_landingpad
+; FNATTRS-SAME: () #[[ATTR4]] personality ptr @__gxx_personality_v0 {
+; FNATTRS-NEXT:    invoke void @do_throw()
+; FNATTRS-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
+; FNATTRS:       lpad:
+; FNATTRS-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
+; FNATTRS-NEXT:            filter [1 x ptr] [ptr @catch_ty]
+; FNATTRS-NEXT:    call void @abort()
+; FNATTRS-NEXT:    unreachable
+; FNATTRS:       unreachable:
+; FNATTRS-NEXT:    unreachable
+;
+; ATTRIBUTOR: Function Attrs: noreturn
+; ATTRIBUTOR-LABEL: define {{[^@]+}}@filter_specific_landingpad
+; ATTRIBUTOR-SAME: () #[[ATTR3]] personality ptr @__gxx_personality_v0 {
+; ATTRIBUTOR-NEXT:    invoke void @do_throw()
+; ATTRIBUTOR-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
+; ATTRIBUTOR:       lpad:
+; ATTRIBUTOR-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
+; ATTRIBUTOR-NEXT:            filter [1 x ptr] [ptr @catch_ty]
+; ATTRIBUTOR-NEXT:    call void @abort()
+; ATTRIBUTOR-NEXT:    unreachable
+; ATTRIBUTOR:       unreachable:
+; ATTRIBUTOR-NEXT:    unreachable
 ;
   invoke void @do_throw()
   to label %unreachable unwind label %lpad
@@ -224,18 +296,31 @@ unreachable:
 }
 
 define void @filter_none_landingpad() personality ptr @__gxx_personality_v0 {
-; COMMON: Function Attrs: noreturn nounwind
-; 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:       lpad:
-; COMMON-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
-; COMMON-NEXT:    filter [0 x ptr] zeroinitializer
-; COMMON-NEXT:    call void @abort()
-; COMMON-NEXT:    unreachable
-; COMMON:       unreachable:
-; COMMON-NEXT:    unreachable
+; FNATTRS: Function Attrs: noreturn nounwind
+; FNATTRS-LABEL: define {{[^@]+}}@filter_none_landingpad
+; FNATTRS-SAME: () #[[ATTR5]] personality ptr @__gxx_personality_v0 {
+; FNATTRS-NEXT:    invoke void @do_throw()
+; FNATTRS-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
+; FNATTRS:       lpad:
+; FNATTRS-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
+; FNATTRS-NEXT:            filter [0 x ptr] zeroinitializer
+; FNATTRS-NEXT:    call void @abort()
+; FNATTRS-NEXT:    unreachable
+; FNATTRS:       unreachable:
+; FNATTRS-NEXT:    unreachable
+;
+; ATTRIBUTOR: Function Attrs: noreturn nounwind
+; ATTRIBUTOR-LABEL: define {{[^@]+}}@filter_none_landingpad
+; ATTRIBUTOR-SAME: () #[[ATTR4]] personality ptr @__gxx_personality_v0 {
+; ATTRIBUTOR-NEXT:    invoke void @do_throw()
+; ATTRIBUTOR-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
+; ATTRIBUTOR:       lpad:
+; ATTRIBUTOR-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
+; ATTRIBUTOR-NEXT:            filter [0 x ptr] zeroinitializer
+; ATTRIBUTOR-NEXT:    call void @abort()
+; ATTRIBUTOR-NEXT:    unreachable
+; ATTRIBUTOR:       unreachable:
+; ATTRIBUTOR-NEXT:    unreachable
 ;
   invoke void @do_throw()
   to label %unreachable unwind label %lpad
@@ -251,18 +336,31 @@ unreachable:
 }
 
 define void @cleanup_landingpad() personality ptr @__gxx_personality_v0 {
-; COMMON: Function Attrs: noreturn
-; 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:       lpad:
-; COMMON-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
-; COMMON-NEXT:    cleanup
-; COMMON-NEXT:    call void @abort()
-; COMMON-NEXT:    unreachable
-; COMMON:       unreachable:
-; COMMON-NEXT:    unreachable
+; FNATTRS: Function Attrs: noreturn
+; FNATTRS-LABEL: define {{[^@]+}}@cleanup_landingpad
+; FNATTRS-SAME: () #[[ATTR4]] personality ptr @__gxx_personality_v0 {
+; FNATTRS-NEXT:    invoke void @do_throw()
+; FNATTRS-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
+; FNATTRS:       lpad:
+; FNATTRS-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
+; FNATTRS-NEXT:            cleanup
+; FNATTRS-NEXT:    call void @abort()
+; FNATTRS-NEXT:    unreachable
+; FNATTRS:       unreachable:
+; FNATTRS-NEXT:    unreachable
+;
+; ATTRIBUTOR: Function Attrs: noreturn
+; ATTRIBUTOR-LABEL: define {{[^@]+}}@cleanup_landingpad
+; ATTRIBUTOR-SAME: () #[[ATTR3]] personality ptr @__gxx_personality_v0 {
+; ATTRIBUTOR-NEXT:    invoke void @do_throw()
+; ATTRIBUTOR-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
+; ATTRIBUTOR:       lpad:
+; ATTRIBUTOR-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
+; ATTRIBUTOR-NEXT:            cleanup
+; ATTRIBUTOR-NEXT:    call void @abort()
+; ATTRIBUTOR-NEXT:    unreachable
+; ATTRIBUTOR:       unreachable:
+; ATTRIBUTOR-NEXT:    unreachable
 ;
   invoke void @do_throw()
   to label %unreachable unwind label %lpad
@@ -280,9 +378,9 @@ unreachable:
 define void @cleanuppad() personality ptr @__gxx_personality_v0 {
 ; FNATTRS: Function Attrs: noreturn
 ; FNATTRS-LABEL: define {{[^@]+}}@cleanuppad
-; FNATTRS-SAME: () #[[ATTR3]] personality ptr @__gxx_personality_v0 {
+; FNATTRS-SAME: () #[[ATTR4]] 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()
@@ -294,7 +392,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()
@@ -317,9 +415,9 @@ unreachable:
 define void @catchswitch_cleanuppad() personality ptr @__gxx_personality_v0 {
 ; FNATTRS: Function Attrs: noreturn
 ; FNATTRS-LABEL: define {{[^@]+}}@catchswitch_cleanuppad
-; FNATTRS-SAME: () #[[ATTR3]] personality ptr @__gxx_personality_v0 {
+; FNATTRS-SAME: () #[[ATTR4]] 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:
@@ -337,7 +435,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:
diff --git a/llvm/test/Transforms/FunctionAttrs/sendmsg-nocallback.ll b/llvm/test/Transforms/FunctionAttrs/sendmsg-nocallback.ll
index 4d5db3263f527..f5fde8822e30b 100644
--- a/llvm/test/Transforms/FunctionAttrs/sendmsg-nocallback.ll
+++ b/llvm/test/Transforms/FunctionAttrs/sendmsg-nocallback.ll
@@ -33,9 +33,9 @@ define internal void @sendmsghalt_is_norecurse() {
 }
 
 define internal i32 @sendmsg_rtn_is_norecurse() {
-; FNATTRS: Function Attrs: mustprogress norecurse nounwind willreturn
+; FNATTRS: Function Attrs: mustprogress nounwind willreturn
 ; FNATTRS-LABEL: define internal i32 @sendmsg_rtn_is_norecurse(
-; FNATTRS-SAME: ) #[[ATTR0]] {
+; FNATTRS-SAME: ) #[[ATTR2:[0-9]+]] {
 ; FNATTRS-NEXT:    [[RES:%.*]] = call i32 @llvm.amdgcn.s.sendmsg.rtn.i32(i32 1)
 ; FNATTRS-NEXT:    ret i32 [[RES]]
 ;
@@ -72,8 +72,9 @@ define void @user() {
 ;.
 ; FNATTRS: attributes #[[ATTR0]] = { mustprogress norecurse nounwind willreturn }
 ; FNATTRS: attributes #[[ATTR1]] = { norecurse nounwind }
-; FNATTRS: attributes #[[ATTR2:[0-9]+]] = { nocallback nounwind willreturn }
-; FNATTRS: attributes #[[ATTR3:[0-9]+]] = { nocallback nounwind }
+; FNATTRS: attributes #[[ATTR2]] = { mustprogress nounwind willreturn }
+; FNATTRS: attributes #[[ATTR3:[0-9]+]] = { nocallback nounwind willreturn }
+; FNATTRS: attributes #[[ATTR4:[0-9]+]] = { nocallback nounwind }
 ;.
 ; ATTRIBUTOR: attributes #[[ATTR0]] = { mustprogress norecurse nounwind willreturn }
 ; ATTRIBUTOR: attributes #[[ATTR1]] = { norecurse nounwind }
diff --git a/llvm/test/Transforms/Inline/cgscc-update.ll b/llvm/test/Transforms/Inline/cgscc-update.ll
index f1121fa88a4b1..f1d6325b7ee5a 100644
--- a/llvm/test/Transforms/Inline/cgscc-update.ll
+++ b/llvm/test/Transforms/Inline/cgscc-update.ll
@@ -11,7 +11,7 @@ declare void @unknown()
 
 ; Basic correctness check: this should get annotated as memory(none).
 ; CHECK: Function Attrs: nounwind memory(none)
-; CHECK-NEXT: declare void @readnone()
+; CHECK-NEXT: declare void @readnone() #0
 declare void @readnone() readnone nounwind
 
 ; The 'test1_' prefixed functions are designed to trigger forming a new direct
@@ -27,8 +27,8 @@ entry:
 }
 
 ; This function should have had 'memory(none)' deduced for its SCC.
-; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none)
-; CHECK-NEXT: define void @test1_g()
+; CHECK: Function Attrs: nofree noinline norecurse nosync nounwind memory(none)
+; CHECK-NEXT: define void @test1_g() #1
 define void @test1_g() noinline {
 entry:
   call void @test1_f(ptr @test1_h)
@@ -37,7 +37,7 @@ entry:
 
 ; This function should have had 'memory(none)' deduced for its SCC.
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none)
-; CHECK-NEXT: define void @test1_h()
+; CHECK-NEXT: define void @test1_h() #2
 define void @test1_h() noinline {
 entry:
   call void @test1_g()
@@ -60,7 +60,7 @@ entry:
 
 ; This function should have had 'memory(none)' deduced for its SCC.
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none)
-; CHECK-NEXT: define void @test2_g()
+; CHECK-NEXT: define void @test2_g() #2
 define void @test2_g() noinline {
 entry:
   %p = call ptr @test2_f()
@@ -70,7 +70,7 @@ entry:
 
 ; This function should have had 'memory(none)' deduced for its SCC.
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none)
-; CHECK-NEXT: define void @test2_h()
+; CHECK-NEXT: define void @test2_h() #2
 define void @test2_h() noinline {
 entry:
   call void @test2_g()
@@ -129,7 +129,7 @@ exit:
 ; doesn't simplify the caller's trivially dead CFG and so we end with a dead
 ; block calling @unknown.
 ; CHECK-NOT: Function Attrs: readnone
-; CHECK: define void @test3_h()
+; CHECK: define void @test3_h() #3
 define void @test3_h() {
 entry:
   %g = call i1 @test3_g(i1 false)
@@ -153,7 +153,7 @@ exit:
 
 ; This function should have had 'memory(none)' deduced for its SCC.
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none)
-; CHECK-NEXT: define void @test4_f1()
+; CHECK-NEXT: define void @test4_f1() #2
 define void @test4_f1() noinline {
 entry:
   call void @test4_h()
@@ -175,8 +175,8 @@ entry:
 }
 
 ; This function should have had 'memory(none)' deduced for its SCC.
-; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none)
-; CHECK-NEXT: define void @test4_h()
+; CHECK: Function Attrs: nofree noinline norecurse nosync nounwind memory(none)
+; CHECK-NEXT: define void @test4_h() #1
 define void @test4_h() noinline {
 entry:
   call void @test4_g(ptr @test4_f2)
diff --git a/llvm/test/Transforms/PhaseOrdering/pr95152.ll b/llvm/test/Transforms/PhaseOrdering/pr95152.ll
index 1e28856f32bd4..66e3ff901c23a 100644
--- a/llvm/test/Transforms/PhaseOrdering/pr95152.ll
+++ b/llvm/test/Transforms/PhaseOrdering/pr95152.ll
@@ -21,7 +21,7 @@ define void @j(ptr %p) optnone noinline {
 
 define void @h(ptr %p) {
 ; CHECK-LABEL: define void @h(
-; CHECK-SAME: ptr initializes((0, 8)) [[P:%.*]]) local_unnamed_addr {
+; CHECK-SAME: ptr initializes((0, 8)) [[P:%.*]]) local_unnamed_addr #[[ATTR1:[0-9]+]] {
 ; CHECK-NEXT:    store i64 3, ptr [[P]], align 4
 ; CHECK-NEXT:    tail call void @j(ptr nonnull [[P]])
 ; CHECK-NEXT:    ret void
@@ -33,7 +33,7 @@ define void @h(ptr %p) {
 
 define void @g(ptr dead_on_unwind noalias writable dereferenceable(8) align 8 %p) minsize {
 ; CHECK-LABEL: define void @g(
-; CHECK-SAME: ptr dead_on_unwind noalias writable writeonly align 8 captures(none) dereferenceable(8) initializes((0, 8)) [[P:%.*]]) local_unnamed_addr #[[ATTR1:[0-9]+]] {
+; CHECK-SAME: ptr dead_on_unwind noalias writable writeonly align 8 captures(none) dereferenceable(8) initializes((0, 8)) [[P:%.*]]) local_unnamed_addr #[[ATTR2:[0-9]+]] {
 ; CHECK-NEXT:    tail call void @h(ptr nonnull [[P]])
 ; CHECK-NEXT:    ret void
 ;
@@ -45,7 +45,7 @@ define void @g(ptr dead_on_unwind noalias writable dereferenceable(8) align 8 %p
 
 define void @f(ptr dead_on_unwind noalias %p) {
 ; CHECK-LABEL: define void @f(
-; CHECK-SAME: ptr dead_on_unwind noalias initializes((0, 8)) [[P:%.*]]) local_unnamed_addr {
+; CHECK-SAME: ptr dead_on_unwind noalias initializes((0, 8)) [[P:%.*]]) local_unnamed_addr #[[ATTR1]] {
 ; CHECK-NEXT:    store i64 3, ptr [[P]], align 4
 ; CHECK-NEXT:    tail call void @j(ptr nonnull align 8 dereferenceable(8) [[P]])
 ; CHECK-NEXT:    store i64 43, ptr [[P]], align 4

>From fa864546deb7a089fe3325c6fca71990e82e3e6a Mon Sep 17 00:00:00 2001
From: Usha Gupta <usha.gupta at arm.com>
Date: Wed, 11 Jun 2025 16:19:02 +0000
Subject: [PATCH 4/5] Infer norecurse for functions with external function
 call/library function callls if none of the user functions have had there
 address taken

---
 .../llvm/Transforms/IPO/FunctionAttrs.h       |  6 ++-
 llvm/lib/Passes/PassBuilderPipelines.cpp      | 12 +++--
 llvm/lib/Transforms/IPO/FunctionAttrs.cpp     | 51 ++++++++++++++++---
 llvm/test/Other/new-pm-lto-defaults.ll        |  5 +-
 4 files changed, 60 insertions(+), 14 deletions(-)

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> {
-  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<PostOrderFunctionAttrsPass> {
 
 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 a99146d5eaa34..1b4fad84d9771 100644
--- a/llvm/lib/Passes/PassBuilderPipelines.cpp
+++ b/llvm/lib/Passes/PassBuilderPipelines.cpp
@@ -975,7 +975,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.
@@ -997,7 +998,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)));
@@ -2075,8 +2077,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 d08a2df9d9e66..9eb103a1dbccd 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -2060,7 +2060,9 @@ static void inferAttrsFromFunctionBodies(const SCCNodeSet &SCCNodes,
 }
 
 static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
-                              SmallSet<Function *, 8> &Changed) {
+                              SmallSet<Function *, 8> &Changed,
+                              bool AnyFunctionsAddressIsTaken,
+                              bool IsLTOPostLink) {
   // Try and identify functions that do not recurse.
 
   // If the SCC contains multiple nodes we know for sure there is recursion.
@@ -2073,6 +2075,10 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
 
   if (F->hasAddressTaken())
     return;
+
+  Module *M = F->getParent();
+  llvm::TargetLibraryInfoImpl TLII(llvm::Triple(M->getTargetTriple()));
+  llvm::TargetLibraryInfo TLI(TLII);
   // 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
@@ -2088,11 +2094,21 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
         if (Callee->doesNotRecurse())
           continue;
 
-        // External call with NoCallback attribute.
+        LibFunc LF;
         if (Callee->isDeclaration()) {
+          // External call with NoCallback attribute.
           if (Callee->hasFnAttribute(Attribute::NoCallback))
             continue;
-          return;
+          // We rely on this only in post link stage when all functions in the
+          // program are known.
+          if (IsLTOPostLink && !AnyFunctionsAddressIsTaken &&
+              TLI.getLibFunc(Callee->getName(), LF))
+            continue;
+          // Do not consider external functions safe unless we are in LTO
+          // post-link stage and have information about all functions in the
+          // program.
+          if (!IsLTOPostLink)
+            return;
         }
       }
     }
@@ -2260,7 +2276,8 @@ static SCCNodesResult createSCCNodeSet(ArrayRef<Function *> Functions) {
 template <typename AARGetterT>
 static SmallSet<Function *, 8>
 deriveAttrsInPostOrder(ArrayRef<Function *> Functions, AARGetterT &&AARGetter,
-                       bool ArgAttrsOnly) {
+                       bool ArgAttrsOnly, bool AnyFunctionAddressTaken = false,
+                       bool IsLTOPostLink = false) {
   SCCNodesResult Nodes = createSCCNodeSet(Functions);
 
   // Bail if the SCC only contains optnone functions.
@@ -2291,7 +2308,8 @@ deriveAttrsInPostOrder(ArrayRef<Function *> Functions, AARGetterT &&AARGetter,
     addNoAliasAttrs(Nodes.SCCNodes, Changed);
     addNonNullAttrs(Nodes.SCCNodes, Changed);
     inferAttrsFromFunctionBodies(Nodes.SCCNodes, Changed);
-    addNoRecurseAttrs(Nodes.SCCNodes, Changed);
+    addNoRecurseAttrs(Nodes.SCCNodes, Changed, AnyFunctionAddressTaken,
+                      IsLTOPostLink);
   }
 
   // Finally, infer the maximal set of attributes from the ones we've inferred
@@ -2334,8 +2352,29 @@ PreservedAnalyses PostOrderFunctionAttrsPass::run(LazyCallGraph::SCC &C,
     Functions.push_back(&N.getFunction());
   }
 
+  bool AnyFunctionsAddressIsTaken = false;
+  // Check if any function in the whole program has its address taken.
+  // We use this information when inferring norecurse attribute: If there is
+  // no function whose address is taken, we conclude that any external function
+  // cannot callback into any user function.
+  if (IsLTOPostLink) {
+    // 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 (F.hasAddressTaken()) {
+        AnyFunctionsAddressIsTaken = true;
+        break; // break if we found one
+      }
+    }
+  }
   auto ChangedFunctions =
-      deriveAttrsInPostOrder(Functions, AARGetter, ArgAttrsOnly);
+      deriveAttrsInPostOrder(Functions, AARGetter, ArgAttrsOnly,
+                             AnyFunctionsAddressIsTaken, IsLTOPostLink);
   if (ChangedFunctions.empty())
     return PreservedAnalyses::all();
 
diff --git a/llvm/test/Other/new-pm-lto-defaults.ll b/llvm/test/Other/new-pm-lto-defaults.ll
index 3aea0f2061f3e..91285e2ec85c6 100644
--- a/llvm/test/Other/new-pm-lto-defaults.ll
+++ b/llvm/test/Other/new-pm-lto-defaults.ll
@@ -166,7 +166,9 @@
 ; CHECK-O-NEXT: Running pass: PrintModulePass
 
 ; Make sure we get the IR back out without changes when we print the module.
-; CHECK-O-LABEL: define void @foo(i32 %n) local_unnamed_addr {
+; CHECK-O23SZ: ; Function Attrs: norecurse
+; CHECK-O1-LABEL: define void @foo(i32 %n) local_unnamed_addr {
+; CHECK-O23SZ-LABEL: define void @foo(i32 %n) local_unnamed_addr #0 {
 ; CHECK-O-NEXT: entry:
 ; CHECK-O-NEXT:   br label %loop
 ; CHECK-O:      loop:
@@ -178,6 +180,7 @@
 ; CHECK-O:      exit:
 ; CHECK-O-NEXT:   ret void
 ; CHECK-O-NEXT: }
+; CHECK-O23SZ: attributes #0 = { norecurse }
 ;
 
 declare void @bar() local_unnamed_addr

>From 5f1dd6f6b77b3183df40670fa0599d0feb2eadd3 Mon Sep 17 00:00:00 2001
From: Usha Gupta <usha.gupta at arm.com>
Date: Fri, 13 Jun 2025 09:53:34 +0000
Subject: [PATCH 5/5] Allow norecurse on functions calling library functions
 only when there are no external references

---
 llvm/lib/Transforms/IPO/FunctionAttrs.cpp | 44 ++++++++++-------------
 llvm/test/Other/new-pm-lto-defaults.ll    |  5 +--
 2 files changed, 19 insertions(+), 30 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index 9eb103a1dbccd..fa64ff7aaea59 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -2061,8 +2061,7 @@ static void inferAttrsFromFunctionBodies(const SCCNodeSet &SCCNodes,
 
 static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
                               SmallSet<Function *, 8> &Changed,
-                              bool AnyFunctionsAddressIsTaken,
-                              bool IsLTOPostLink) {
+                              bool NoFunctionsAddressIsTaken) {
   // Try and identify functions that do not recurse.
 
   // If the SCC contains multiple nodes we know for sure there is recursion.
@@ -2073,9 +2072,6 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
   if (!F || !F->hasExactDefinition() || F->doesNotRecurse())
     return;
 
-  if (F->hasAddressTaken())
-    return;
-
   Module *M = F->getParent();
   llvm::TargetLibraryInfoImpl TLII(llvm::Triple(M->getTargetTriple()));
   llvm::TargetLibraryInfo TLI(TLII);
@@ -2086,6 +2082,9 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
   for (auto &BB : *F) {
     for (auto &I : BB.instructionsWithoutDebug()) {
       if (auto *CB = dyn_cast<CallBase>(&I)) {
+        if (F->hasAddressTaken())
+          return;
+
         Function *Callee = CB->getCalledFunction();
 
         if (!Callee || Callee == F)
@@ -2096,19 +2095,11 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
 
         LibFunc LF;
         if (Callee->isDeclaration()) {
-          // External call with NoCallback attribute.
-          if (Callee->hasFnAttribute(Attribute::NoCallback))
+          if (Callee->hasFnAttribute(Attribute::NoCallback) ||
+              (NoFunctionsAddressIsTaken &&
+               TLI.getLibFunc(Callee->getName(), LF)))
             continue;
-          // We rely on this only in post link stage when all functions in the
-          // program are known.
-          if (IsLTOPostLink && !AnyFunctionsAddressIsTaken &&
-              TLI.getLibFunc(Callee->getName(), LF))
-            continue;
-          // Do not consider external functions safe unless we are in LTO
-          // post-link stage and have information about all functions in the
-          // program.
-          if (!IsLTOPostLink)
-            return;
+          return;
         }
       }
     }
@@ -2276,8 +2267,8 @@ static SCCNodesResult createSCCNodeSet(ArrayRef<Function *> Functions) {
 template <typename AARGetterT>
 static SmallSet<Function *, 8>
 deriveAttrsInPostOrder(ArrayRef<Function *> Functions, AARGetterT &&AARGetter,
-                       bool ArgAttrsOnly, bool AnyFunctionAddressTaken = false,
-                       bool IsLTOPostLink = false) {
+                       bool ArgAttrsOnly,
+                       bool NoFunctionAddressIsTaken = false) {
   SCCNodesResult Nodes = createSCCNodeSet(Functions);
 
   // Bail if the SCC only contains optnone functions.
@@ -2308,8 +2299,7 @@ deriveAttrsInPostOrder(ArrayRef<Function *> Functions, AARGetterT &&AARGetter,
     addNoAliasAttrs(Nodes.SCCNodes, Changed);
     addNonNullAttrs(Nodes.SCCNodes, Changed);
     inferAttrsFromFunctionBodies(Nodes.SCCNodes, Changed);
-    addNoRecurseAttrs(Nodes.SCCNodes, Changed, AnyFunctionAddressTaken,
-                      IsLTOPostLink);
+    addNoRecurseAttrs(Nodes.SCCNodes, Changed, NoFunctionAddressIsTaken);
   }
 
   // Finally, infer the maximal set of attributes from the ones we've inferred
@@ -2352,12 +2342,13 @@ PreservedAnalyses PostOrderFunctionAttrsPass::run(LazyCallGraph::SCC &C,
     Functions.push_back(&N.getFunction());
   }
 
-  bool AnyFunctionsAddressIsTaken = false;
+  bool NoFunctionsAddressIsTaken = false;
   // Check if any function in the whole program has its address taken.
   // We use this information when inferring norecurse attribute: If there is
   // no function whose address is taken, we conclude that any external function
   // cannot callback into any user function.
   if (IsLTOPostLink) {
+    bool AnyFunctionsAddressIsTaken = false;
     // Get the parent Module of the Function
     Module &M = *C.begin()->getFunction().getParent();
     for (Function &F : M) {
@@ -2366,15 +2357,16 @@ PreservedAnalyses PostOrderFunctionAttrsPass::run(LazyCallGraph::SCC &C,
       if (F.isDeclaration())
         continue;
 
-      if (F.hasAddressTaken()) {
+      if (!F.hasLocalLinkage() || F.hasAddressTaken()) {
         AnyFunctionsAddressIsTaken = true;
         break; // break if we found one
       }
     }
+    NoFunctionsAddressIsTaken = !AnyFunctionsAddressIsTaken;
   }
-  auto ChangedFunctions =
-      deriveAttrsInPostOrder(Functions, AARGetter, ArgAttrsOnly,
-                             AnyFunctionsAddressIsTaken, IsLTOPostLink);
+  auto ChangedFunctions = deriveAttrsInPostOrder(
+      Functions, AARGetter, ArgAttrsOnly, NoFunctionsAddressIsTaken);
+
   if (ChangedFunctions.empty())
     return PreservedAnalyses::all();
 
diff --git a/llvm/test/Other/new-pm-lto-defaults.ll b/llvm/test/Other/new-pm-lto-defaults.ll
index 91285e2ec85c6..3aea0f2061f3e 100644
--- a/llvm/test/Other/new-pm-lto-defaults.ll
+++ b/llvm/test/Other/new-pm-lto-defaults.ll
@@ -166,9 +166,7 @@
 ; CHECK-O-NEXT: Running pass: PrintModulePass
 
 ; Make sure we get the IR back out without changes when we print the module.
-; CHECK-O23SZ: ; Function Attrs: norecurse
-; CHECK-O1-LABEL: define void @foo(i32 %n) local_unnamed_addr {
-; CHECK-O23SZ-LABEL: define void @foo(i32 %n) local_unnamed_addr #0 {
+; CHECK-O-LABEL: define void @foo(i32 %n) local_unnamed_addr {
 ; CHECK-O-NEXT: entry:
 ; CHECK-O-NEXT:   br label %loop
 ; CHECK-O:      loop:
@@ -180,7 +178,6 @@
 ; CHECK-O:      exit:
 ; CHECK-O-NEXT:   ret void
 ; CHECK-O-NEXT: }
-; CHECK-O23SZ: attributes #0 = { norecurse }
 ;
 
 declare void @bar() local_unnamed_addr



More information about the llvm-commits mailing list