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

Usha Gupta via llvm-commits llvm-commits at lists.llvm.org
Fri Jun 20 04:52:48 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/9] [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/9] 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/9] 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/9] 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/9] 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

>From 8df2493b435016c5c0e735185c03d306385f3d03 Mon Sep 17 00:00:00 2001
From: Usha Gupta <usha.gupta at arm.com>
Date: Mon, 16 Jun 2025 11:24:07 +0000
Subject: [PATCH 6/9] Check for functions's LocalLinkage when applying
 norecurse attribute

---
 llvm/lib/Transforms/IPO/FunctionAttrs.cpp     |  21 +-
 .../Other/cgscc-iterate-function-mutation.ll  |  40 ++--
 .../FunctionAttrs/nofree-attributor.ll        |   4 +-
 llvm/test/Transforms/FunctionAttrs/nonnull.ll |   2 +-
 .../norecurse_multiSCC_indirect_recursion.ll  |  40 ++--
 .../norecurse_multiSCC_indirect_recursion1.ll |  43 ++--
 .../norecurse_self_recursive_callee.ll        |  36 ++--
 .../test/Transforms/FunctionAttrs/nounwind.ll | 193 ++++++------------
 .../FunctionAttrs/sendmsg-nocallback.ll       |   9 +-
 llvm/test/Transforms/Inline/cgscc-update.ll   |  20 +-
 llvm/test/Transforms/PhaseOrdering/pr95152.ll |   6 +-
 11 files changed, 171 insertions(+), 243 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index fa64ff7aaea59..de671c48f572e 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -2063,7 +2063,6 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
                               SmallSet<Function *, 8> &Changed,
                               bool NoFunctionsAddressIsTaken) {
   // Try and identify functions that do not recurse.
-
   // If the SCC contains multiple nodes we know for sure there is recursion.
   if (SCCNodes.size() != 1)
     return;
@@ -2072,9 +2071,6 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
   if (!F || !F->hasExactDefinition() || F->doesNotRecurse())
     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
@@ -2082,9 +2078,6 @@ 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)
@@ -2093,21 +2086,25 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
         if (Callee->doesNotRecurse())
           continue;
 
-        LibFunc LF;
         if (Callee->isDeclaration()) {
           if (Callee->hasFnAttribute(Attribute::NoCallback) ||
-              (NoFunctionsAddressIsTaken &&
-               TLI.getLibFunc(Callee->getName(), LF)))
+              NoFunctionsAddressIsTaken)
             continue;
           return;
+        } else if (F->hasAddressTaken() || !F->hasLocalLinkage()) {
+          // Control reaches here only for callees which are defined in this
+          // module and do not satisfy conditions for norecurse attribute.
+          // In such a case, if function F has external linkage or address
+          // taken, conversatively avoid adding norecurse.
+          return;
         }
       }
     }
   }
 
   // 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.
+  // call to this function or a direct call to internal function. Also, SCC is
+  // one. Together, the above checks ensures, this function cannot norecurse.
   F->setDoesNotRecurse();
   ++NumNoRecurse;
   Changed.insert(F);
diff --git a/llvm/test/Other/cgscc-iterate-function-mutation.ll b/llvm/test/Other/cgscc-iterate-function-mutation.ll
index 631674362bb09..aafd38d1e8825 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() #1 {
+; CHECK: define void @test1_a() {
 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() #1 {
+; CHECK: define void @test3_a() {
 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() #2 {
+; CHECK: define void @test3_b11() #0 {
 define void @test3_b11() {
   call void @test3_b12()
   ret void
 }
 
-; CHECK: define void @test3_b12() #2 {
+; CHECK: define void @test3_b12() #0 {
 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() #2 {
+; CHECK: define void @test3_b21() #0 {
 define void @test3_b21() {
   call void @test3_b22()
   ret void
 }
 
-; CHECK: define void @test3_b22() #2 {
+; CHECK: define void @test3_b22() #0 {
 define void @test3_b22() {
   call void @test3_b23()
   ret void
@@ -180,13 +180,13 @@ exit:
   ret void
 }
 
-; CHECK: define void @test3_b31() #1 {
+; CHECK: define void @test3_b31() {
 define void @test3_b31() {
   call void @test3_b32()
   ret void
 }
 
-; CHECK: define void @test3_b32() #1 {
+; CHECK: define void @test3_b32() {
 define void @test3_b32() {
   call void @test3_b33()
   ret void
@@ -205,13 +205,13 @@ exit:
   ret void
 }
 
-; CHECK: define void @test3_b41() #2 {
+; CHECK: define void @test3_b41() #0 {
 define void @test3_b41() {
   call void @test3_b42()
   ret void
 }
 
-; CHECK: define void @test3_b42() #2 {
+; CHECK: define void @test3_b42() #0 {
 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() #2 {
+; CHECK: define void @test4_b11() #0 {
 define void @test4_b11() {
   call void @test4_b12()
   ret void
 }
 
-; CHECK: define void @test4_b12() #2 {
+; CHECK: define void @test4_b12() #0 {
 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() #2 {
+; CHECK: define void @test4_b21() #0 {
 define void @test4_b21() {
   call void @test4_b22()
   ret void
 }
 
-; CHECK: define void @test4_b22() #2 {
+; CHECK: define void @test4_b22() #0 {
 define void @test4_b22() {
   call void @test4_b23()
   ret void
 }
 
 ; CHECK: define void @test4_b23() #0 {
-define void @test4_b23() #0 {
+define void @test4_b23() {
   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() #1 {
+; CHECK: define void @test4_b31() {
 define void @test4_b31() {
   call void @test4_b32()
   ret void
 }
 
-; CHECK: define void @test4_b32() #1 {
+; CHECK: define void @test4_b32() {
 define void @test4_b32() {
   call void @test4_b33()
   ret void
@@ -313,13 +313,13 @@ exit:
   ret void
 }
 
-; CHECK: define void @test4_b41() #2 {
+; CHECK: define void @test4_b41() #0 {
 define void @test4_b41() {
   call void @test4_b42()
   ret void
 }
 
-; CHECK: define void @test4_b42() #2 {
+; CHECK: define void @test4_b42() #0 {
 define void @test4_b42() {
   call void @test4_b43()
   ret void
@@ -339,5 +339,3 @@ 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 1457e96e1af68..5a0b6ef1e3043 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 norecurse nosync nounwind memory(none) uwtable
+; FNATTR: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 ; FNATTR-LABEL: define {{[^@]+}}@f2
-; FNATTR-SAME: () #[[ATTR7:[0-9]+]] {
+; FNATTR-SAME: () #[[ATTR4]] {
 ; 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 b484210d75957..483b560ece6c8 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:%.*]]) #[[ATTR13:[0-9]+]] {
+; FNATTRS-SAME: ptr nonnull dereferenceable(8) [[A:%.*]]) {
 ; 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/norecurse_multiSCC_indirect_recursion.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion.ll
index c0af16b069bde..3f616b1bf76f5 100644
--- a/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion.ll
+++ b/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion.ll
@@ -3,8 +3,8 @@
 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 
+; 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
@@ -13,9 +13,9 @@ target triple = "aarch64-unknown-linux-gnu"
 ; 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 {
+define internal void @bar1() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define dso_local void @bar1(
+; CHECK-LABEL: define internal void @bar1(
 ; CHECK-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[CALL:%.*]] = tail call i32 @main()
@@ -45,9 +45,9 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-define dso_local void @foo1() local_unnamed_addr #0 {
+define internal void @foo1() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define dso_local void @foo1(
+; CHECK-LABEL: define internal void @foo1(
 ; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @bar1()
@@ -59,9 +59,9 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-define dso_local void @bar() local_unnamed_addr #0 {
+define internal void @bar() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define dso_local void @bar(
+; CHECK-LABEL: define internal void @bar(
 ; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @foo1()
@@ -73,9 +73,9 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-define dso_local void @foo() local_unnamed_addr #0 {
+define internal void @foo() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define dso_local void @foo(
+; CHECK-LABEL: define internal void @foo(
 ; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @bar()
@@ -87,9 +87,9 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-define dso_local void @bar4() local_unnamed_addr #0 {
+define internal void @bar4() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define dso_local void @bar4(
+; CHECK-LABEL: define internal void @bar4(
 ; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @bar2()
@@ -101,9 +101,9 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-define dso_local void @bar2() local_unnamed_addr #0 {
+define internal void @bar2() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define dso_local void @bar2(
+; CHECK-LABEL: define internal void @bar2(
 ; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @bar3()
@@ -115,9 +115,9 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-define dso_local void @bar3() local_unnamed_addr #0 {
+define internal void @bar3() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define dso_local void @bar3(
+; CHECK-LABEL: define internal void @bar3(
 ; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @bar4()
@@ -129,9 +129,9 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-define dso_local void @fun() local_unnamed_addr #0 {
+define internal void @fun() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define dso_local void @fun(
+; CHECK-LABEL: define internal void @fun(
 ; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @baz()
@@ -143,9 +143,9 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-define dso_local void @baz() local_unnamed_addr #0 {
+define internal void @baz() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define dso_local void @baz(
+; CHECK-LABEL: define internal void @baz(
 ; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @fun()
diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion1.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion1.ll
index 60bbaab2a7d66..e35ad4101fecb 100644
--- a/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion1.ll
+++ b/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion1.ll
@@ -3,19 +3,20 @@
 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. 
+; 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.
+; chain. but does not call back main() and hence main() can be marked as
+; norecurse. But main() does not have internal linkage, hence we avoid adding 
+; norecurse for main() as well.
 
 ; 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: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 ; CHECK-LABEL: define dso_local noundef i32 @main(
 ; CHECK-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
@@ -30,10 +31,10 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-define dso_local void @bar4() local_unnamed_addr #0 {
+define internal 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-LABEL: define internal void @bar4(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @bar2()
 ; CHECK-NEXT:    ret void
@@ -44,10 +45,10 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-define dso_local void @bar2() local_unnamed_addr #0 {
+define internal 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-LABEL: define internal void @bar2(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @bar3()
 ; CHECK-NEXT:    ret void
@@ -58,10 +59,10 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-define dso_local void @bar3() local_unnamed_addr #0 {
+define internal 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-LABEL: define internal void @bar3(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @bar4()
 ; CHECK-NEXT:    ret void
@@ -72,10 +73,10 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-define dso_local void @fun() local_unnamed_addr #0 {
+define internal 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-LABEL: define internal void @fun(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @baz()
 ; CHECK-NEXT:    ret void
@@ -86,10 +87,10 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-define dso_local void @baz() local_unnamed_addr #0 {
+define internal 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-LABEL: define internal void @baz(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @fun()
 ; CHECK-NEXT:    ret void
diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll
index 9b87c4e4ad76c..e151e7178026a 100644
--- a/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll
+++ b/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll
@@ -3,10 +3,10 @@
 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 
+; 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.
 
@@ -14,9 +14,9 @@ target triple = "aarch64-unknown-linux-gnu"
 @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 {
+define internal 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-LABEL: define internal 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]+]]
@@ -32,9 +32,9 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nounwind uwtable
-define dso_local void @callee5(i32 noundef %x) local_unnamed_addr #1 {
+define internal void @callee5(i32 noundef %x) local_unnamed_addr #1 {
 ; CHECK: Function Attrs: nofree noinline nounwind uwtable
-; CHECK-LABEL: define dso_local void @callee5(
+; CHECK-LABEL: define internal void @callee5(
 ; CHECK-SAME: i32 noundef [[X:%.*]]) local_unnamed_addr #[[ATTR1:[0-9]+]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i32 [[X]], 0
@@ -60,9 +60,9 @@ if.end:                                           ; preds = %if.then, %entry
 }
 
 ; Function Attrs: nofree noinline nounwind uwtable
-define dso_local void @callee4() local_unnamed_addr #1 {
+define internal void @callee4() local_unnamed_addr #1 {
 ; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
-; CHECK-LABEL: define dso_local void @callee4(
+; CHECK-LABEL: define internal 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]]
@@ -76,9 +76,9 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nounwind uwtable
-define dso_local void @callee3() local_unnamed_addr #1 {
+define internal void @callee3() local_unnamed_addr #1 {
 ; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
-; CHECK-LABEL: define dso_local void @callee3(
+; CHECK-LABEL: define internal void @callee3(
 ; CHECK-SAME: ) local_unnamed_addr #[[ATTR2]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @callee4()
@@ -90,9 +90,9 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nounwind uwtable
-define dso_local void @callee2() local_unnamed_addr #1 {
+define internal void @callee2() local_unnamed_addr #1 {
 ; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
-; CHECK-LABEL: define dso_local void @callee2(
+; CHECK-LABEL: define internal void @callee2(
 ; CHECK-SAME: ) local_unnamed_addr #[[ATTR2]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @callee3()
@@ -104,9 +104,9 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nounwind uwtable
-define dso_local void @callee1() local_unnamed_addr #1 {
+define internal void @callee1() local_unnamed_addr #1 {
 ; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
-; CHECK-LABEL: define dso_local void @callee1(
+; CHECK-LABEL: define internal void @callee1(
 ; CHECK-SAME: ) local_unnamed_addr #[[ATTR2]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @callee2()
@@ -118,9 +118,9 @@ entry:
 }
 
 ; Function Attrs: nofree noinline nounwind uwtable
-define dso_local void @bob() local_unnamed_addr #1 {
+define internal void @bob() local_unnamed_addr #1 {
 ; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
-; CHECK-LABEL: define dso_local void @bob(
+; CHECK-LABEL: define internal void @bob(
 ; CHECK-SAME: ) local_unnamed_addr #[[ATTR2]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    tail call void @callee1()
diff --git a/llvm/test/Transforms/FunctionAttrs/nounwind.ll b/llvm/test/Transforms/FunctionAttrs/nounwind.ll
index b613c47435a03..ecba7a0318cd2 100644
--- a/llvm/test/Transforms/FunctionAttrs/nounwind.ll
+++ b/llvm/test/Transforms/FunctionAttrs/nounwind.ll
@@ -157,9 +157,7 @@ define i32 @catch_thing() personality ptr @__gxx_personality_v0 {
 }
 
 define i32 @catch_thing_user() {
-; FNATTRS: Function Attrs: norecurse
-; FNATTRS-LABEL: define {{[^@]+}}@catch_thing_user
-; FNATTRS-SAME: () #[[ATTR2:[0-9]+]] {
+; FNATTRS-LABEL: define {{[^@]+}}@catch_thing_user() {
 ; FNATTRS-NEXT:    [[CATCH_THING_CALL:%.*]] = call i32 @catch_thing()
 ; FNATTRS-NEXT:    ret i32 [[CATCH_THING_CALL]]
 ;
@@ -176,31 +174,18 @@ declare void @abort() nounwind
 @catch_ty = external global ptr
 
 define void @catch_specific_landingpad() personality ptr @__gxx_personality_v0 {
-; 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
+; 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
 ;
   invoke void @do_throw()
   to label %unreachable unwind label %lpad
@@ -216,31 +201,18 @@ unreachable:
 }
 
 define void @catch_all_landingpad() personality ptr @__gxx_personality_v0 {
-; 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
+; 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
 ;
   invoke void @do_throw()
   to label %unreachable unwind label %lpad
@@ -256,31 +228,18 @@ unreachable:
 }
 
 define void @filter_specific_landingpad() personality ptr @__gxx_personality_v0 {
-; 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
+; 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
 ;
   invoke void @do_throw()
   to label %unreachable unwind label %lpad
@@ -296,31 +255,18 @@ unreachable:
 }
 
 define void @filter_none_landingpad() personality ptr @__gxx_personality_v0 {
-; 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
+; 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
 ;
   invoke void @do_throw()
   to label %unreachable unwind label %lpad
@@ -336,31 +282,18 @@ unreachable:
 }
 
 define void @cleanup_landingpad() personality ptr @__gxx_personality_v0 {
-; 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
+; 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
 ;
   invoke void @do_throw()
   to label %unreachable unwind label %lpad
@@ -378,7 +311,7 @@ unreachable:
 define void @cleanuppad() personality ptr @__gxx_personality_v0 {
 ; FNATTRS: Function Attrs: noreturn
 ; FNATTRS-LABEL: define {{[^@]+}}@cleanuppad
-; FNATTRS-SAME: () #[[ATTR4]] personality ptr @__gxx_personality_v0 {
+; FNATTRS-SAME: () #[[ATTR3]] personality ptr @__gxx_personality_v0 {
 ; FNATTRS-NEXT:    invoke void @do_throw()
 ; FNATTRS-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[CPAD:%.*]]
 ; FNATTRS:       cpad:
@@ -415,7 +348,7 @@ unreachable:
 define void @catchswitch_cleanuppad() personality ptr @__gxx_personality_v0 {
 ; FNATTRS: Function Attrs: noreturn
 ; FNATTRS-LABEL: define {{[^@]+}}@catchswitch_cleanuppad
-; FNATTRS-SAME: () #[[ATTR4]] personality ptr @__gxx_personality_v0 {
+; FNATTRS-SAME: () #[[ATTR3]] personality ptr @__gxx_personality_v0 {
 ; FNATTRS-NEXT:    invoke void @do_throw()
 ; FNATTRS-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[CS:%.*]]
 ; FNATTRS:       cs:
diff --git a/llvm/test/Transforms/FunctionAttrs/sendmsg-nocallback.ll b/llvm/test/Transforms/FunctionAttrs/sendmsg-nocallback.ll
index f5fde8822e30b..4d5db3263f527 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 nounwind willreturn
+; FNATTRS: Function Attrs: mustprogress norecurse nounwind willreturn
 ; FNATTRS-LABEL: define internal i32 @sendmsg_rtn_is_norecurse(
-; FNATTRS-SAME: ) #[[ATTR2:[0-9]+]] {
+; FNATTRS-SAME: ) #[[ATTR0]] {
 ; FNATTRS-NEXT:    [[RES:%.*]] = call i32 @llvm.amdgcn.s.sendmsg.rtn.i32(i32 1)
 ; FNATTRS-NEXT:    ret i32 [[RES]]
 ;
@@ -72,9 +72,8 @@ define void @user() {
 ;.
 ; FNATTRS: attributes #[[ATTR0]] = { mustprogress norecurse nounwind willreturn }
 ; FNATTRS: attributes #[[ATTR1]] = { norecurse nounwind }
-; FNATTRS: attributes #[[ATTR2]] = { mustprogress nounwind willreturn }
-; FNATTRS: attributes #[[ATTR3:[0-9]+]] = { nocallback nounwind willreturn }
-; FNATTRS: attributes #[[ATTR4:[0-9]+]] = { nocallback nounwind }
+; FNATTRS: attributes #[[ATTR2:[0-9]+]] = { nocallback nounwind willreturn }
+; FNATTRS: attributes #[[ATTR3:[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 f1d6325b7ee5a..f1121fa88a4b1 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() #0
+; CHECK-NEXT: declare void @readnone()
 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 norecurse nosync nounwind memory(none)
-; CHECK-NEXT: define void @test1_g() #1
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none)
+; CHECK-NEXT: define void @test1_g()
 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() #2
+; CHECK-NEXT: define void @test1_h()
 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() #2
+; CHECK-NEXT: define void @test2_g()
 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() #2
+; CHECK-NEXT: define void @test2_h()
 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() #3
+; CHECK: define void @test3_h()
 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() #2
+; CHECK-NEXT: define void @test4_f1()
 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 norecurse nosync nounwind memory(none)
-; CHECK-NEXT: define void @test4_h() #1
+; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none)
+; CHECK-NEXT: define void @test4_h()
 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 66e3ff901c23a..1e28856f32bd4 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 #[[ATTR1:[0-9]+]] {
+; CHECK-SAME: ptr initializes((0, 8)) [[P:%.*]]) local_unnamed_addr {
 ; 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 #[[ATTR2:[0-9]+]] {
+; 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-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 #[[ATTR1]] {
+; CHECK-SAME: ptr dead_on_unwind noalias initializes((0, 8)) [[P:%.*]]) local_unnamed_addr {
 ; 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 d4fa041e561d158c2e7a23e62b854352dc46cd36 Mon Sep 17 00:00:00 2001
From: Usha Gupta <usha.gupta at arm.com>
Date: Thu, 19 Jun 2025 10:36:09 +0000
Subject: [PATCH 7/9] Apply optimizations based on linkage and function address
 only during LTO to avoid incorrect inference pre-LTO

---
 llvm/lib/Transforms/IPO/FunctionAttrs.cpp     | 36 ++++---
 .../norecurse_multiSCC_indirect_recursion.ll  | 64 ++++++------
 .../norecurse_multiSCC_indirect_recursion1.ll | 69 +++++++------
 .../norecurse_self_recursive_callee.ll        | 98 +++++++------------
 4 files changed, 130 insertions(+), 137 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index de671c48f572e..3d6daafb0e11c 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -2086,18 +2086,16 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
         if (Callee->doesNotRecurse())
           continue;
 
-        if (Callee->isDeclaration()) {
-          if (Callee->hasFnAttribute(Attribute::NoCallback) ||
-              NoFunctionsAddressIsTaken)
-            continue;
-          return;
-        } else if (F->hasAddressTaken() || !F->hasLocalLinkage()) {
-          // Control reaches here only for callees which are defined in this
-          // module and do not satisfy conditions for norecurse attribute.
-          // In such a case, if function F has external linkage or address
-          // taken, conversatively avoid adding norecurse.
-          return;
-        }
+        // If there are no functions with external linkage and none of the
+        // functions' address is taken, it ensures that this Callee does not
+        // have any path leading back to the Caller F.
+        // The 'NoFunctionsAddressIsTaken' flag is only set during post-link
+        // LTO phase after examining all available function definitions.
+        if (NoFunctionsAddressIsTaken ||
+            (Callee->isDeclaration() &&
+             Callee->hasFnAttribute(Attribute::NoCallback)))
+          continue;
+        return;
       }
     }
   }
@@ -2340,10 +2338,11 @@ PreservedAnalyses PostOrderFunctionAttrsPass::run(LazyCallGraph::SCC &C,
   }
 
   bool NoFunctionsAddressIsTaken = false;
-  // Check if any function in the whole program has its address taken.
+  // 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, we conclude that any external function
-  // cannot callback into any user function.
+  // no function whose address is taken and all functions have internal
+  // linkage, there is no path for a callback to any user function.
   if (IsLTOPostLink) {
     bool AnyFunctionsAddressIsTaken = false;
     // Get the parent Module of the Function
@@ -2354,6 +2353,12 @@ PreservedAnalyses PostOrderFunctionAttrsPass::run(LazyCallGraph::SCC &C,
       if (F.isDeclaration())
         continue;
 
+      // If the function is already marked as norecurse, this should not block
+      // norecurse inference even though it may have external linkage.
+      // For ex: main() in C++.
+      if (F.doesNotRecurse())
+        continue;
+
       if (!F.hasLocalLinkage() || F.hasAddressTaken()) {
         AnyFunctionsAddressIsTaken = true;
         break; // break if we found one
@@ -2361,6 +2366,7 @@ PreservedAnalyses PostOrderFunctionAttrsPass::run(LazyCallGraph::SCC &C,
     }
     NoFunctionsAddressIsTaken = !AnyFunctionsAddressIsTaken;
   }
+
   auto ChangedFunctions = deriveAttrsInPostOrder(
       Functions, AARGetter, ArgAttrsOnly, NoFunctionsAddressIsTaken);
 
diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion.ll
index 3f616b1bf76f5..d5b7323413eee 100644
--- a/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion.ll
+++ b/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion.ll
@@ -1,7 +1,5 @@
 ; 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"
+; RUN: opt < %s -passes="lto<O2>" -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
@@ -15,8 +13,8 @@ target triple = "aarch64-unknown-linux-gnu"
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 define internal void @bar1() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define internal void @bar1(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+; CHECK-LABEL: define internal fastcc void @bar1(
+; CHECK-SAME: ) unnamed_addr #[[ATTR0:[0-9]+]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[CALL:%.*]] = tail call i32 @main()
 ; CHECK-NEXT:    ret void
@@ -32,9 +30,9 @@ define dso_local noundef i32 @main() local_unnamed_addr #0 {
 ; 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:    tail call fastcc void @foo()
+; CHECK-NEXT:    tail call fastcc void @bar2()
+; CHECK-NEXT:    tail call fastcc void @baz()
 ; CHECK-NEXT:    ret i32 0
 ;
 entry:
@@ -47,10 +45,10 @@ entry:
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 define internal void @foo1() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define internal void @foo1(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
+; CHECK-LABEL: define internal fastcc void @foo1(
+; CHECK-SAME: ) unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @bar1()
+; CHECK-NEXT:    tail call fastcc void @bar1()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -61,10 +59,10 @@ entry:
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 define internal void @bar() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define internal void @bar(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
+; CHECK-LABEL: define internal fastcc void @bar(
+; CHECK-SAME: ) unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @foo1()
+; CHECK-NEXT:    tail call fastcc void @foo1()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -75,10 +73,10 @@ entry:
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 define internal void @foo() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define internal void @foo(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
+; CHECK-LABEL: define internal fastcc void @foo(
+; CHECK-SAME: ) unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @bar()
+; CHECK-NEXT:    tail call fastcc void @bar()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -89,10 +87,10 @@ entry:
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 define internal void @bar4() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define internal void @bar4(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
+; CHECK-LABEL: define internal fastcc void @bar4(
+; CHECK-SAME: ) unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @bar2()
+; CHECK-NEXT:    tail call fastcc void @bar2()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -103,10 +101,10 @@ entry:
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 define internal void @bar2() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define internal void @bar2(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
+; CHECK-LABEL: define internal fastcc void @bar2(
+; CHECK-SAME: ) unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @bar3()
+; CHECK-NEXT:    tail call fastcc void @bar3()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -117,10 +115,10 @@ entry:
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 define internal void @bar3() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define internal void @bar3(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
+; CHECK-LABEL: define internal fastcc void @bar3(
+; CHECK-SAME: ) unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @bar4()
+; CHECK-NEXT:    tail call fastcc void @bar4()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -131,10 +129,10 @@ entry:
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 define internal void @fun() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define internal void @fun(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
+; CHECK-LABEL: define internal fastcc void @fun(
+; CHECK-SAME: ) unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @baz()
+; CHECK-NEXT:    tail call fastcc void @baz()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -145,10 +143,10 @@ entry:
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 define internal void @baz() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define internal void @baz(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
+; CHECK-LABEL: define internal fastcc void @baz(
+; CHECK-SAME: ) unnamed_addr #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @fun()
+; CHECK-NEXT:    tail call fastcc void @fun()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -156,4 +154,4 @@ entry:
   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" }
+attributes #0 = { nofree noinline nosync nounwind memory(none) uwtable }
diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion1.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion1.ll
index e35ad4101fecb..d4f37b69bfacf 100644
--- a/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion1.ll
+++ b/llvm/test/Transforms/FunctionAttrs/norecurse_multiSCC_indirect_recursion1.ll
@@ -1,7 +1,5 @@
 ; 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"
+; RUN: opt < %s -passes="lto<O2>" -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
@@ -9,19 +7,33 @@ target triple = "aarch64-unknown-linux-gnu"
 ; 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. But main() does not have internal linkage, hence we avoid adding 
-; norecurse for main() as well.
+; f1() calls bar2 and baz, both of which are part of some indirect recursive
+; chain. but does not call back f1() and hence f1() can be marked as
+; norecurse.
 
-; Function Attrs: nofree noinline 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
+; Function Attrs: nofree noinline norecurse nosync nounwind memory(none) uwtable
+define dso_local noundef i32 @main() local_unnamed_addr #1 {
+; 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:    [[TMP0:%.*]] = tail call fastcc i32 @f1()
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  tail call void @f1()
+  ret i32 0
+}
+
+
+; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
+define internal i32 @f1() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline norecurse nosync nounwind memory(none) uwtable
+; CHECK-LABEL: define internal fastcc noundef i32 @f1(
+; CHECK-SAME: ) unnamed_addr #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    tail call fastcc void @bar2()
+; CHECK-NEXT:    tail call fastcc void @baz()
 ; CHECK-NEXT:    ret i32 0
 ;
 entry:
@@ -33,10 +45,10 @@ entry:
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 define internal void @bar4() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define internal void @bar4(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
+; CHECK-LABEL: define internal fastcc void @bar4(
+; CHECK-SAME: ) unnamed_addr #[[ATTR1:[0-9]+]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @bar2()
+; CHECK-NEXT:    tail call fastcc void @bar2()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -47,10 +59,10 @@ entry:
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 define internal void @bar2() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define internal void @bar2(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
+; CHECK-LABEL: define internal fastcc void @bar2(
+; CHECK-SAME: ) unnamed_addr #[[ATTR1]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @bar3()
+; CHECK-NEXT:    tail call fastcc void @bar3()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -61,10 +73,10 @@ entry:
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 define internal void @bar3() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define internal void @bar3(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
+; CHECK-LABEL: define internal fastcc void @bar3(
+; CHECK-SAME: ) unnamed_addr #[[ATTR1]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @bar4()
+; CHECK-NEXT:    tail call fastcc void @bar4()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -75,10 +87,10 @@ entry:
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 define internal void @fun() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define internal void @fun(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
+; CHECK-LABEL: define internal fastcc void @fun(
+; CHECK-SAME: ) unnamed_addr #[[ATTR1]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @baz()
+; CHECK-NEXT:    tail call fastcc void @baz()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -89,10 +101,10 @@ entry:
 ; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
 define internal void @baz() local_unnamed_addr #0 {
 ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
-; CHECK-LABEL: define internal void @baz(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
+; CHECK-LABEL: define internal fastcc void @baz(
+; CHECK-SAME: ) unnamed_addr #[[ATTR1]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @fun()
+; CHECK-NEXT:    tail call fastcc void @fun()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -100,4 +112,5 @@ entry:
   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" }
+attributes #0 = { nofree noinline nosync nounwind memory(none) uwtable }
+attributes #1 = { nofree noinline norecurse nosync nounwind memory(none) uwtable }
diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll
index e151e7178026a..c3e897d056441 100644
--- a/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll
+++ b/llvm/test/Transforms/FunctionAttrs/norecurse_self_recursive_callee.ll
@@ -1,7 +1,5 @@
 ; 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"
+; RUN: opt < %s -passes="lto<O2>" -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
@@ -13,37 +11,37 @@ target triple = "aarch64-unknown-linux-gnu"
 @x = dso_local global i32 4, align 4
 @y = dso_local global i32 2, align 4
 
-; Function Attrs: nofree noinline norecurse nounwind memory(readwrite, argmem: none) uwtable
-define internal void @callee6() local_unnamed_addr #0 {
+; Function Attrs: nofree noinline nounwind uwtable
+define internal void @callee6() local_unnamed_addr #1 {
 ; CHECK: Function Attrs: nofree noinline norecurse nounwind memory(readwrite, argmem: none) uwtable
-; CHECK-LABEL: define internal void @callee6(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+; CHECK-LABEL: define internal fastcc void @callee6(
+; CHECK-SAME: ) unnamed_addr #[[ATTR0:[0-9]+]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[TMP0:%.*]] = load volatile i32, ptr @y, align 4, !tbaa [[TBAA8:![0-9]+]]
+; 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, !tbaa [[TBAA8]]
+; CHECK-NEXT:    store volatile i32 [[INC]], ptr @y, align 4
 ; CHECK-NEXT:    ret void
 ;
 entry:
-  %0 = load volatile i32, ptr @y, align 4, !tbaa !8
+  %0 = load volatile i32, ptr @y, align 4
   %inc = add nsw i32 %0, 1
-  store volatile i32 %inc, ptr @y, align 4, !tbaa !8
+  store volatile i32 %inc, ptr @y, align 4
   ret void
 }
 
 ; Function Attrs: nofree noinline nounwind uwtable
 define internal void @callee5(i32 noundef %x) local_unnamed_addr #1 {
 ; CHECK: Function Attrs: nofree noinline nounwind uwtable
-; CHECK-LABEL: define internal void @callee5(
-; CHECK-SAME: i32 noundef [[X:%.*]]) local_unnamed_addr #[[ATTR1:[0-9]+]] {
+; CHECK-LABEL: define internal fastcc void @callee5(
+; CHECK-SAME: i32 noundef [[X:%.*]]) 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:    tail call fastcc void @callee5(i32 noundef [[X]])
 ; CHECK-NEXT:    br label %[[IF_END]]
 ; CHECK:       [[IF_END]]:
-; CHECK-NEXT:    tail call void @callee6()
+; CHECK-NEXT:    tail call fastcc void @callee6()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -62,15 +60,15 @@ if.end:                                           ; preds = %if.then, %entry
 ; Function Attrs: nofree noinline nounwind uwtable
 define internal void @callee4() local_unnamed_addr #1 {
 ; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
-; CHECK-LABEL: define internal void @callee4(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR2:[0-9]+]] {
+; CHECK-LABEL: define internal fastcc void @callee4(
+; CHECK-SAME: ) 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:    [[TMP0:%.*]] = load volatile i32, ptr @x, align 4
+; CHECK-NEXT:    tail call fastcc void @callee5(i32 noundef [[TMP0]])
 ; CHECK-NEXT:    ret void
 ;
 entry:
-  %0 = load volatile i32, ptr @x, align 4, !tbaa !8
+  %0 = load volatile i32, ptr @x, align 4
   tail call void @callee5(i32 noundef %0)
   ret void
 }
@@ -78,10 +76,10 @@ entry:
 ; Function Attrs: nofree noinline nounwind uwtable
 define internal void @callee3() local_unnamed_addr #1 {
 ; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
-; CHECK-LABEL: define internal void @callee3(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR2]] {
+; CHECK-LABEL: define internal fastcc void @callee3(
+; CHECK-SAME: ) unnamed_addr #[[ATTR2]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @callee4()
+; CHECK-NEXT:    tail call fastcc void @callee4()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -92,10 +90,10 @@ entry:
 ; Function Attrs: nofree noinline nounwind uwtable
 define internal void @callee2() local_unnamed_addr #1 {
 ; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
-; CHECK-LABEL: define internal void @callee2(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR2]] {
+; CHECK-LABEL: define internal fastcc void @callee2(
+; CHECK-SAME: ) unnamed_addr #[[ATTR2]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @callee3()
+; CHECK-NEXT:    tail call fastcc void @callee3()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -106,10 +104,10 @@ entry:
 ; Function Attrs: nofree noinline nounwind uwtable
 define internal void @callee1() local_unnamed_addr #1 {
 ; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
-; CHECK-LABEL: define internal void @callee1(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR2]] {
+; CHECK-LABEL: define internal fastcc void @callee1(
+; CHECK-SAME: ) unnamed_addr #[[ATTR2]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @callee2()
+; CHECK-NEXT:    tail call fastcc void @callee2()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -120,10 +118,10 @@ entry:
 ; Function Attrs: nofree noinline nounwind uwtable
 define internal void @bob() local_unnamed_addr #1 {
 ; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
-; CHECK-LABEL: define internal void @bob(
-; CHECK-SAME: ) local_unnamed_addr #[[ATTR2]] {
+; CHECK-LABEL: define internal fastcc void @bob(
+; CHECK-SAME: ) unnamed_addr #[[ATTR2]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    tail call void @callee1()
+; CHECK-NEXT:    tail call fastcc void @callee1()
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -131,13 +129,13 @@ entry:
   ret void
 }
 
-; Function Attrs: nofree nounwind uwtable
-define dso_local noundef i32 @main() local_unnamed_addr #2 {
+; Function Attrs: nofree norecurse nounwind uwtable
+define dso_local noundef i32 @main() local_unnamed_addr #3 {
 ; 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:    tail call fastcc void @bob()
 ; CHECK-NEXT:    ret i32 0
 ;
 entry:
@@ -145,29 +143,7 @@ entry:
   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"}
-;.
+attributes #0 = { nofree noinline norecurse nounwind memory(readwrite, argmem: none) uwtable }
+attributes #1 = { nofree noinline nounwind uwtable }
+attributes #2 = { nofree nounwind uwtable }
+attributes #3 = { nofree norecurse nounwind uwtable }

>From cfe4a3916eaf92fd04756ffeb25c581e5bd0c14a Mon Sep 17 00:00:00 2001
From: Usha Gupta <usha.gupta at arm.com>
Date: Thu, 19 Jun 2025 13:31:40 +0000
Subject: [PATCH 8/9] Revert changes in unaffected tests

---
 llvm/lib/Transforms/IPO/FunctionAttrs.cpp     |   1 +
 .../AMDGPU/amdgpu-simplify-libcall-sincos.ll  |   8 +-
 .../test/Transforms/FunctionAttrs/noreturn.ll |   8 +-
 .../test/Transforms/FunctionAttrs/nounwind.ll | 113 +++++++-----------
 4 files changed, 50 insertions(+), 80 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index 3d6daafb0e11c..871470743255e 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -2063,6 +2063,7 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
                               SmallSet<Function *, 8> &Changed,
                               bool NoFunctionsAddressIsTaken) {
   // Try and identify functions that do not recurse.
+
   // If the SCC contains multiple nodes we know for sure there is recursion.
   if (SCCNodes.size() != 1)
     return;
diff --git a/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-sincos.ll b/llvm/test/CodeGen/AMDGPU/amdgpu-simplify-libcall-sincos.ll
index 6e2b825a45f56..34777eff0e856 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:%.*]]) #[[ATTR6:[0-9]+]] {
+; 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-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 #[[ATTR7:[0-9]+]] {
+; CHECK-SAME: (float [[X:%.*]]) local_unnamed_addr #[[ATTR6:[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 #[[ATTR7]] {
+; CHECK-SAME: (float [[X:%.*]]) local_unnamed_addr #[[ATTR6]] {
 ; 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 #[[ATTR8:[0-9]+]] {
+; CHECK-SAME: (float [[X:%.*]], ptr addrspace(1) [[SIN_OUT:%.*]], ptr addrspace(1) [[COS_OUT:%.*]]) local_unnamed_addr #[[ATTR7:[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/Transforms/FunctionAttrs/noreturn.ll b/llvm/test/Transforms/FunctionAttrs/noreturn.ll
index 965467ba6015f..fa80f6c2eced4 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 ecba7a0318cd2..afa9ae31b5fba 100644
--- a/llvm/test/Transforms/FunctionAttrs/nounwind.ll
+++ b/llvm/test/Transforms/FunctionAttrs/nounwind.ll
@@ -4,15 +4,10 @@
 
 ; TEST 1
 define i32 @foo1() {
-; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
-; FNATTRS-LABEL: define {{[^@]+}}@foo1
-; FNATTRS-SAME: () #[[ATTR0:[0-9]+]] {
-; FNATTRS-NEXT:    ret i32 1
-;
-; ATTRIBUTOR: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
-; ATTRIBUTOR-LABEL: define {{[^@]+}}@foo1
-; ATTRIBUTOR-SAME: () #[[ATTR0:[0-9]+]] {
-; ATTRIBUTOR-NEXT:    ret i32 1
+; COMMON: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
+; COMMON-LABEL: define {{[^@]+}}@foo1
+; COMMON-SAME: () #[[ATTR0:[0-9]+]] {
+; COMMON-NEXT:    ret i32 1
 ;
   ret i32 1
 }
@@ -75,23 +70,14 @@ define void @call_non_nounwind(){
 ; }
 
 define i32 @maybe_throw(i1 zeroext %0) {
-; FNATTRS-LABEL: define {{[^@]+}}@maybe_throw
-; FNATTRS-SAME: (i1 zeroext [[TMP0:%.*]]) {
-; FNATTRS-NEXT:    br i1 [[TMP0]], label [[TMP2:%.*]], label [[TMP3:%.*]]
-; FNATTRS:       2:
-; FNATTRS-NEXT:    tail call void @__cxa_rethrow()
-; FNATTRS-NEXT:    unreachable
-; FNATTRS:       3:
-; FNATTRS-NEXT:    ret i32 -1
-;
-; ATTRIBUTOR-LABEL: define {{[^@]+}}@maybe_throw
-; ATTRIBUTOR-SAME: (i1 zeroext [[TMP0:%.*]]) {
-; ATTRIBUTOR-NEXT:    br i1 [[TMP0]], label [[TMP2:%.*]], label [[TMP3:%.*]]
-; ATTRIBUTOR:       2:
-; ATTRIBUTOR-NEXT:    tail call void @__cxa_rethrow()
-; ATTRIBUTOR-NEXT:    unreachable
-; ATTRIBUTOR:       3:
-; ATTRIBUTOR-NEXT:    ret i32 -1
+; COMMON-LABEL: define {{[^@]+}}@maybe_throw
+; COMMON-SAME: (i1 zeroext [[TMP0:%.*]]) {
+; COMMON-NEXT:    br i1 [[TMP0]], label [[TMP2:%.*]], label [[TMP3:%.*]]
+; COMMON:       2:
+; COMMON-NEXT:    tail call void @__cxa_rethrow()
+; COMMON-NEXT:    unreachable
+; COMMON:       3:
+; COMMON-NEXT:    ret i32 -1
 ;
   br i1 %0, label %2, label %3
 
@@ -115,31 +101,18 @@ declare void @__cxa_rethrow()
 ; }
 
 define i32 @catch_thing() personality ptr @__gxx_personality_v0 {
-; FNATTRS-LABEL: define {{[^@]+}}@catch_thing() personality ptr @__gxx_personality_v0 {
-; FNATTRS-NEXT:    invoke void @__cxa_rethrow()
-; FNATTRS-NEXT:            to label [[TMP1:%.*]] unwind label [[TMP2:%.*]]
-; FNATTRS:       1:
-; FNATTRS-NEXT:    unreachable
-; FNATTRS:       2:
-; FNATTRS-NEXT:    [[TMP3:%.*]] = landingpad { ptr, i32 }
-; FNATTRS-NEXT:            catch ptr null
-; FNATTRS-NEXT:    [[TMP4:%.*]] = extractvalue { ptr, i32 } [[TMP3]], 0
-; FNATTRS-NEXT:    [[TMP5:%.*]] = tail call ptr @__cxa_begin_catch(ptr [[TMP4]])
-; FNATTRS-NEXT:    tail call void @__cxa_end_catch()
-; FNATTRS-NEXT:    ret i32 -1
-;
-; ATTRIBUTOR-LABEL: define {{[^@]+}}@catch_thing() personality ptr @__gxx_personality_v0 {
-; ATTRIBUTOR-NEXT:    invoke void @__cxa_rethrow()
-; ATTRIBUTOR-NEXT:            to label [[TMP1:%.*]] unwind label [[TMP2:%.*]]
-; ATTRIBUTOR:       1:
-; ATTRIBUTOR-NEXT:    unreachable
-; ATTRIBUTOR:       2:
-; ATTRIBUTOR-NEXT:    [[TMP3:%.*]] = landingpad { ptr, i32 }
-; ATTRIBUTOR-NEXT:            catch ptr null
-; ATTRIBUTOR-NEXT:    [[TMP4:%.*]] = extractvalue { ptr, i32 } [[TMP3]], 0
-; ATTRIBUTOR-NEXT:    [[TMP5:%.*]] = tail call ptr @__cxa_begin_catch(ptr [[TMP4]])
-; ATTRIBUTOR-NEXT:    tail call void @__cxa_end_catch()
-; ATTRIBUTOR-NEXT:    ret i32 -1
+; COMMON-LABEL: define {{[^@]+}}@catch_thing() personality ptr @__gxx_personality_v0 {
+; COMMON-NEXT:    invoke void @__cxa_rethrow()
+; COMMON-NEXT:    to label [[TMP1:%.*]] unwind label [[TMP2:%.*]]
+; COMMON:       1:
+; COMMON-NEXT:    unreachable
+; COMMON:       2:
+; COMMON-NEXT:    [[TMP3:%.*]] = landingpad { ptr, i32 }
+; COMMON-NEXT:    catch ptr null
+; COMMON-NEXT:    [[TMP4:%.*]] = extractvalue { ptr, i32 } [[TMP3]], 0
+; COMMON-NEXT:    [[TMP5:%.*]] = tail call ptr @__cxa_begin_catch(ptr [[TMP4]])
+; COMMON-NEXT:    tail call void @__cxa_end_catch()
+; COMMON-NEXT:    ret i32 -1
 ;
   invoke void @__cxa_rethrow() #1
   to label %1 unwind label %2
@@ -157,13 +130,9 @@ define i32 @catch_thing() personality ptr @__gxx_personality_v0 {
 }
 
 define i32 @catch_thing_user() {
-; FNATTRS-LABEL: define {{[^@]+}}@catch_thing_user() {
-; FNATTRS-NEXT:    [[CATCH_THING_CALL:%.*]] = call i32 @catch_thing()
-; FNATTRS-NEXT:    ret i32 [[CATCH_THING_CALL]]
-;
-; ATTRIBUTOR-LABEL: define {{[^@]+}}@catch_thing_user() {
-; ATTRIBUTOR-NEXT:    [[CATCH_THING_CALL:%.*]] = call i32 @catch_thing()
-; ATTRIBUTOR-NEXT:    ret i32 [[CATCH_THING_CALL]]
+; COMMON-LABEL: define {{[^@]+}}@catch_thing_user() {
+; COMMON-NEXT:    [[CATCH_THING_CALL:%.*]] = call i32 @catch_thing()
+; COMMON-NEXT:    ret i32 [[CATCH_THING_CALL]]
 ;
   %catch_thing_call = call i32 @catch_thing()
   ret i32 %catch_thing_call
@@ -178,10 +147,10 @@ define void @catch_specific_landingpad() personality ptr @__gxx_personality_v0 {
 ; COMMON-LABEL: define {{[^@]+}}@catch_specific_landingpad
 ; COMMON-SAME: () #[[ATTR3:[0-9]+]] personality ptr @__gxx_personality_v0 {
 ; COMMON-NEXT:    invoke void @do_throw()
-; COMMON-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
+; COMMON-NEXT:    to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
 ; COMMON:       lpad:
 ; COMMON-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
-; COMMON-NEXT:            catch ptr @catch_ty
+; COMMON-NEXT:    catch ptr @catch_ty
 ; COMMON-NEXT:    call void @abort()
 ; COMMON-NEXT:    unreachable
 ; COMMON:       unreachable:
@@ -205,10 +174,10 @@ define void @catch_all_landingpad() personality ptr @__gxx_personality_v0 {
 ; COMMON-LABEL: define {{[^@]+}}@catch_all_landingpad
 ; COMMON-SAME: () #[[ATTR4:[0-9]+]] personality ptr @__gxx_personality_v0 {
 ; COMMON-NEXT:    invoke void @do_throw()
-; COMMON-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
+; COMMON-NEXT:    to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
 ; COMMON:       lpad:
 ; COMMON-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
-; COMMON-NEXT:            catch ptr null
+; COMMON-NEXT:    catch ptr null
 ; COMMON-NEXT:    call void @abort()
 ; COMMON-NEXT:    unreachable
 ; COMMON:       unreachable:
@@ -232,10 +201,10 @@ define void @filter_specific_landingpad() personality ptr @__gxx_personality_v0
 ; COMMON-LABEL: define {{[^@]+}}@filter_specific_landingpad
 ; COMMON-SAME: () #[[ATTR3]] personality ptr @__gxx_personality_v0 {
 ; COMMON-NEXT:    invoke void @do_throw()
-; COMMON-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
+; COMMON-NEXT:    to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
 ; COMMON:       lpad:
 ; COMMON-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
-; COMMON-NEXT:            filter [1 x ptr] [ptr @catch_ty]
+; COMMON-NEXT:    filter [1 x ptr] [ptr @catch_ty]
 ; COMMON-NEXT:    call void @abort()
 ; COMMON-NEXT:    unreachable
 ; COMMON:       unreachable:
@@ -259,10 +228,10 @@ define void @filter_none_landingpad() personality ptr @__gxx_personality_v0 {
 ; COMMON-LABEL: define {{[^@]+}}@filter_none_landingpad
 ; COMMON-SAME: () #[[ATTR4]] personality ptr @__gxx_personality_v0 {
 ; COMMON-NEXT:    invoke void @do_throw()
-; COMMON-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
+; COMMON-NEXT:    to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
 ; COMMON:       lpad:
 ; COMMON-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
-; COMMON-NEXT:            filter [0 x ptr] zeroinitializer
+; COMMON-NEXT:    filter [0 x ptr] zeroinitializer
 ; COMMON-NEXT:    call void @abort()
 ; COMMON-NEXT:    unreachable
 ; COMMON:       unreachable:
@@ -286,10 +255,10 @@ define void @cleanup_landingpad() personality ptr @__gxx_personality_v0 {
 ; COMMON-LABEL: define {{[^@]+}}@cleanup_landingpad
 ; COMMON-SAME: () #[[ATTR3]] personality ptr @__gxx_personality_v0 {
 ; COMMON-NEXT:    invoke void @do_throw()
-; COMMON-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
+; COMMON-NEXT:    to label [[UNREACHABLE:%.*]] unwind label [[LPAD:%.*]]
 ; COMMON:       lpad:
 ; COMMON-NEXT:    [[LP:%.*]] = landingpad { ptr, i32 }
-; COMMON-NEXT:            cleanup
+; COMMON-NEXT:    cleanup
 ; COMMON-NEXT:    call void @abort()
 ; COMMON-NEXT:    unreachable
 ; COMMON:       unreachable:
@@ -313,7 +282,7 @@ define void @cleanuppad() personality ptr @__gxx_personality_v0 {
 ; FNATTRS-LABEL: define {{[^@]+}}@cleanuppad
 ; FNATTRS-SAME: () #[[ATTR3]] personality ptr @__gxx_personality_v0 {
 ; FNATTRS-NEXT:    invoke void @do_throw()
-; FNATTRS-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[CPAD:%.*]]
+; FNATTRS-NEXT:    to label [[UNREACHABLE:%.*]] unwind label [[CPAD:%.*]]
 ; FNATTRS:       cpad:
 ; FNATTRS-NEXT:    [[CP:%.*]] = cleanuppad within none []
 ; FNATTRS-NEXT:    call void @abort()
@@ -325,7 +294,7 @@ define void @cleanuppad() personality ptr @__gxx_personality_v0 {
 ; ATTRIBUTOR-LABEL: define {{[^@]+}}@cleanuppad
 ; ATTRIBUTOR-SAME: () #[[ATTR4]] personality ptr @__gxx_personality_v0 {
 ; ATTRIBUTOR-NEXT:    invoke void @do_throw()
-; ATTRIBUTOR-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[CPAD:%.*]]
+; ATTRIBUTOR-NEXT:    to label [[UNREACHABLE:%.*]] unwind label [[CPAD:%.*]]
 ; ATTRIBUTOR:       cpad:
 ; ATTRIBUTOR-NEXT:    [[CP:%.*]] = cleanuppad within none []
 ; ATTRIBUTOR-NEXT:    call void @abort()
@@ -350,7 +319,7 @@ define void @catchswitch_cleanuppad() personality ptr @__gxx_personality_v0 {
 ; FNATTRS-LABEL: define {{[^@]+}}@catchswitch_cleanuppad
 ; FNATTRS-SAME: () #[[ATTR3]] personality ptr @__gxx_personality_v0 {
 ; FNATTRS-NEXT:    invoke void @do_throw()
-; FNATTRS-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[CS:%.*]]
+; FNATTRS-NEXT:    to label [[UNREACHABLE:%.*]] unwind label [[CS:%.*]]
 ; FNATTRS:       cs:
 ; FNATTRS-NEXT:    [[TOK:%.*]] = catchswitch within none [label %catch] unwind label [[CPAD:%.*]]
 ; FNATTRS:       catch:
@@ -368,7 +337,7 @@ define void @catchswitch_cleanuppad() personality ptr @__gxx_personality_v0 {
 ; ATTRIBUTOR-LABEL: define {{[^@]+}}@catchswitch_cleanuppad
 ; ATTRIBUTOR-SAME: () #[[ATTR4]] personality ptr @__gxx_personality_v0 {
 ; ATTRIBUTOR-NEXT:    invoke void @do_throw()
-; ATTRIBUTOR-NEXT:            to label [[UNREACHABLE:%.*]] unwind label [[CS:%.*]]
+; ATTRIBUTOR-NEXT:    to label [[UNREACHABLE:%.*]] unwind label [[CS:%.*]]
 ; ATTRIBUTOR:       cs:
 ; ATTRIBUTOR-NEXT:    [[TOK:%.*]] = catchswitch within none [label %catch] unwind label [[CPAD:%.*]]
 ; ATTRIBUTOR:       catch:

>From e89c42f844a47c1c05789fff1ab221cace680585 Mon Sep 17 00:00:00 2001
From: Usha Gupta <usha.gupta at arm.com>
Date: Fri, 20 Jun 2025 11:46:48 +0000
Subject: [PATCH 9/9] Additional test with a call to library function without
 NoCallback

---
 .../Transforms/FunctionAttrs/norecurse_lto.ll | 36 +++++++++++++++++++
 1 file changed, 36 insertions(+)
 create mode 100644 llvm/test/Transforms/FunctionAttrs/norecurse_lto.ll

diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse_lto.ll b/llvm/test/Transforms/FunctionAttrs/norecurse_lto.ll
new file mode 100644
index 0000000000000..d29dfe0feb94e
--- /dev/null
+++ b/llvm/test/Transforms/FunctionAttrs/norecurse_lto.ll
@@ -0,0 +1,36 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5
+; RUN: opt < %s -passes="lto<O2>" -S | FileCheck %s
+
+ at .str = private unnamed_addr constant [7 x i8] c"Hello \00", align 1
+
+; Function Attrs: nofree noinline nounwind uwtable vscale_range(1,16)
+define internal void @bob() local_unnamed_addr #0 {
+; CHECK: Function Attrs: nofree noinline norecurse nounwind uwtable
+; CHECK-LABEL: define internal fastcc void @bob(
+; CHECK-SAME: ) unnamed_addr #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:    [[TMP1:%.*]] = tail call i32 (ptr, ...) @printf(ptr noundef nonnull dereferenceable(1) @.str)
+; CHECK-NEXT:    ret void
+;
+  %1 = tail call i32 (ptr, ...) @printf(ptr noundef nonnull dereferenceable(1) @.str)
+  ret void
+}
+
+; Function Attrs: nofree nounwind
+declare noundef i32 @printf(ptr nocapture noundef readonly, ...) local_unnamed_addr #1
+
+; Function Attrs: nofree norecurse nounwind uwtable vscale_range(1,16)
+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 #[[ATTR2:[0-9]+]] {
+; CHECK-NEXT:    tail call fastcc void @bob()
+; CHECK-NEXT:    ret i32 0
+;
+  tail call void @bob()
+  ret i32 0
+}
+
+
+attributes #0 = { nofree noinline nounwind uwtable }
+attributes #1 = { nofree nounwind }
+attributes #2 = { nofree norecurse nounwind uwtable }



More information about the llvm-commits mailing list