[llvm] [FuncAttrs] Relax norecurse attribute inference (PR #139943)
Usha Gupta via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 11 09:27:57 PDT 2025
https://github.com/usha1830 updated https://github.com/llvm/llvm-project/pull/139943
>From 19e14e203ad963b21e5fd9e2f1c3f634613e07fc 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/4] [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 13b786caac61b2e11383424112c25ed6f3d8081f 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/4] 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 c3b2fefaa432767ee3634a67983f0f4e7f525918 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/4] 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 d05198c67cc88b6104eac7358544befe45be3106 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/4] 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 6a21ff616d506..b2a21b134c381 100644
--- a/llvm/include/llvm/Transforms/IPO/FunctionAttrs.h
+++ b/llvm/include/llvm/Transforms/IPO/FunctionAttrs.h
@@ -47,8 +47,9 @@ 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) {}
PreservedAnalyses run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM,
LazyCallGraph &CG, CGSCCUpdateResult &UR);
@@ -57,6 +58,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 5a85b308925a6..7e9463f96ff11 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
@@ -1911,7 +1913,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)));
@@ -2072,8 +2074,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
More information about the llvm-commits
mailing list