Skip to content

Commit d148825

Browse files
committed
[FuncAttrs] Relax norecurse attribute when callee is self-recursive
or is part of a recursive call chain which does not include the caller.
1 parent 0a5012f commit d148825

File tree

4 files changed

+509
-20
lines changed

4 files changed

+509
-20
lines changed

llvm/lib/Transforms/IPO/FunctionAttrs.cpp

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2067,8 +2067,49 @@ static void inferAttrsFromFunctionBodies(const SCCNodeSet &SCCNodes,
20672067
AI.run(SCCNodes, Changed);
20682068
}
20692069

2070+
/// Returns true if N or any function it (transitively) calls makes a call
2071+
/// to an unknown external function: either an indirect call or a declaration
2072+
/// without the NoCallback attribute.
2073+
static bool callsUnknownExternal(LazyCallGraph::Node *N) {
2074+
std::deque<LazyCallGraph::Node *> Worklist;
2075+
DenseSet<LazyCallGraph::Node *> Visited;
2076+
Worklist.push_back(N);
2077+
Visited.insert(N);
2078+
2079+
while (!Worklist.empty()) {
2080+
auto *Cur = Worklist.front();
2081+
Worklist.pop_front();
2082+
2083+
Function &F = Cur->getFunction();
2084+
for (auto &BB : F) {
2085+
for (auto &I : BB.instructionsWithoutDebug()) {
2086+
if (auto *CB = dyn_cast<CallBase>(&I)) {
2087+
const Function *Callee = CB->getCalledFunction();
2088+
// Indirect call, treat as unknown external function.
2089+
if (!Callee)
2090+
return true;
2091+
// Extern declaration without NoCallback attribute
2092+
if (Callee->isDeclaration() &&
2093+
!Callee->hasFnAttribute(Attribute::NoCallback))
2094+
return true;
2095+
}
2096+
}
2097+
}
2098+
2099+
// Enqueue all direct call-edge successors for further scanning
2100+
for (auto &Edge : Cur->populate().calls()) {
2101+
LazyCallGraph::Node *Succ = &Edge.getNode();
2102+
if (Visited.insert(Succ).second)
2103+
Worklist.push_back(Succ);
2104+
}
2105+
}
2106+
2107+
return false;
2108+
}
2109+
20702110
static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
2071-
SmallPtrSet<Function *, 8> &Changed) {
2111+
SmallPtrSet<Function *, 8> &Changed,
2112+
LazyCallGraph &CG) {
20722113
// Try and identify functions that do not recurse.
20732114

20742115
// If the SCC contains multiple nodes we know for sure there is recursion.
@@ -2079,24 +2120,35 @@ static void addNoRecurseAttrs(const SCCNodeSet &SCCNodes,
20792120
if (!F || !F->hasExactDefinition() || F->doesNotRecurse())
20802121
return;
20812122

2082-
// If all of the calls in F are identifiable and are to norecurse functions, F
2083-
// is norecurse. This check also detects self-recursion as F is not currently
2084-
// marked norecurse, so any called from F to F will not be marked norecurse.
2085-
for (auto &BB : *F)
2086-
for (auto &I : BB.instructionsWithoutDebug())
2123+
// If all of the calls in F are identifiable and can be proven to not
2124+
// callback F, F is norecurse. This check also detects self-recursion
2125+
// as F is not currently marked norecurse, so any call from F to F
2126+
// will not be marked norecurse.
2127+
for (auto &BB : *F) {
2128+
for (auto &I : BB.instructionsWithoutDebug()) {
20872129
if (auto *CB = dyn_cast<CallBase>(&I)) {
20882130
Function *Callee = CB->getCalledFunction();
2089-
if (!Callee || Callee == F ||
2090-
(!Callee->doesNotRecurse() &&
2091-
!(Callee->isDeclaration() &&
2092-
Callee->hasFnAttribute(Attribute::NoCallback))))
2093-
// Function calls a potentially recursive function.
2131+
2132+
if (!Callee || Callee == F)
2133+
return;
2134+
2135+
// External call with NoCallback attribute.
2136+
if (Callee->isDeclaration()) {
2137+
if (Callee->hasFnAttribute(Attribute::NoCallback))
2138+
continue;
20942139
return;
2140+
}
2141+
2142+
if (auto *CNode = CG.lookup(*Callee))
2143+
if (callsUnknownExternal(CNode))
2144+
return;
20952145
}
2146+
}
2147+
}
20962148

2097-
// Every call was to a non-recursive function other than this function, and
2098-
// we have no indirect recursion as the SCC size is one. This function cannot
2099-
// recurse.
2149+
// Every call was either to an external function guaranteed to not make a
2150+
// call to this function or a direct call to internal function and we have no
2151+
// indirect recursion as the SCC size is one. This function cannot recurse.
21002152
F->setDoesNotRecurse();
21012153
++NumNoRecurse;
21022154
Changed.insert(F);
@@ -2240,7 +2292,7 @@ static SCCNodesResult createSCCNodeSet(ArrayRef<Function *> Functions) {
22402292
template <typename AARGetterT>
22412293
static SmallPtrSet<Function *, 8>
22422294
deriveAttrsInPostOrder(ArrayRef<Function *> Functions, AARGetterT &&AARGetter,
2243-
bool ArgAttrsOnly) {
2295+
bool ArgAttrsOnly, LazyCallGraph &CG) {
22442296
SCCNodesResult Nodes = createSCCNodeSet(Functions);
22452297

22462298
// Bail if the SCC only contains optnone functions.
@@ -2264,10 +2316,13 @@ deriveAttrsInPostOrder(ArrayRef<Function *> Functions, AARGetterT &&AARGetter,
22642316
addColdAttrs(Nodes.SCCNodes, Changed);
22652317
addWillReturn(Nodes.SCCNodes, Changed);
22662318
addNoUndefAttrs(Nodes.SCCNodes, Changed);
2267-
addNoAliasAttrs(Nodes.SCCNodes, Changed);
2268-
addNonNullAttrs(Nodes.SCCNodes, Changed);
2269-
inferAttrsFromFunctionBodies(Nodes.SCCNodes, Changed);
2270-
addNoRecurseAttrs(Nodes.SCCNodes, Changed);
2319+
2320+
// If we have no external nodes participating in the SCC, we can deduce some
2321+
// more precise attributes as well.
2322+
addNoAliasAttrs(Nodes.SCCNodes, Changed);
2323+
addNonNullAttrs(Nodes.SCCNodes, Changed);
2324+
inferAttrsFromFunctionBodies(Nodes.SCCNodes, Changed);
2325+
addNoRecurseAttrs(Nodes.SCCNodes, Changed, CG);
22712326

22722327
// Finally, infer the maximal set of attributes from the ones we've inferred
22732328
// above. This is handling the cases where one attribute on a signature
@@ -2310,7 +2365,7 @@ PreservedAnalyses PostOrderFunctionAttrsPass::run(LazyCallGraph::SCC &C,
23102365
}
23112366

23122367
auto ChangedFunctions =
2313-
deriveAttrsInPostOrder(Functions, AARGetter, ArgAttrsOnly);
2368+
deriveAttrsInPostOrder(Functions, AARGetter, ArgAttrsOnly, CG);
23142369
if (ChangedFunctions.empty())
23152370
return PreservedAnalyses::all();
23162371

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5
2+
; RUN: opt < %s -passes=function-attrs -S | FileCheck %s
3+
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"
4+
target triple = "aarch64-unknown-linux-gnu"
5+
6+
; This test includes a call graph with multiple SCCs. The purpose of this is
7+
; to check that norecurse is not added when a function is part of non-singular
8+
; SCC.
9+
; There are three different SCCs in this test:
10+
; SCC#1: main, foo, bar, foo1, bar1
11+
; SCC#2: bar2, bar3, bar4
12+
; SCC#3: baz, fun
13+
; None of these functions should be marked as norecurse
14+
15+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
16+
define dso_local void @bar1() local_unnamed_addr #0 {
17+
; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
18+
; CHECK-LABEL: define dso_local void @bar1(
19+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] {
20+
; CHECK-NEXT: [[ENTRY:.*:]]
21+
; CHECK-NEXT: [[CALL:%.*]] = tail call i32 @main()
22+
; CHECK-NEXT: ret void
23+
;
24+
entry:
25+
%call = tail call i32 @main()
26+
ret void
27+
}
28+
29+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
30+
define dso_local noundef i32 @main() local_unnamed_addr #0 {
31+
; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
32+
; CHECK-LABEL: define dso_local noundef i32 @main(
33+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
34+
; CHECK-NEXT: [[ENTRY:.*:]]
35+
; CHECK-NEXT: tail call void @foo()
36+
; CHECK-NEXT: tail call void @bar2()
37+
; CHECK-NEXT: tail call void @baz()
38+
; CHECK-NEXT: ret i32 0
39+
;
40+
entry:
41+
tail call void @foo()
42+
tail call void @bar2()
43+
tail call void @baz()
44+
ret i32 0
45+
}
46+
47+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
48+
define dso_local void @foo1() local_unnamed_addr #0 {
49+
; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
50+
; CHECK-LABEL: define dso_local void @foo1(
51+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
52+
; CHECK-NEXT: [[ENTRY:.*:]]
53+
; CHECK-NEXT: tail call void @bar1()
54+
; CHECK-NEXT: ret void
55+
;
56+
entry:
57+
tail call void @bar1()
58+
ret void
59+
}
60+
61+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
62+
define dso_local void @bar() local_unnamed_addr #0 {
63+
; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
64+
; CHECK-LABEL: define dso_local void @bar(
65+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
66+
; CHECK-NEXT: [[ENTRY:.*:]]
67+
; CHECK-NEXT: tail call void @foo1()
68+
; CHECK-NEXT: ret void
69+
;
70+
entry:
71+
tail call void @foo1()
72+
ret void
73+
}
74+
75+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
76+
define dso_local void @foo() local_unnamed_addr #0 {
77+
; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
78+
; CHECK-LABEL: define dso_local void @foo(
79+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
80+
; CHECK-NEXT: [[ENTRY:.*:]]
81+
; CHECK-NEXT: tail call void @bar()
82+
; CHECK-NEXT: ret void
83+
;
84+
entry:
85+
tail call void @bar()
86+
ret void
87+
}
88+
89+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
90+
define dso_local void @bar4() local_unnamed_addr #0 {
91+
; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
92+
; CHECK-LABEL: define dso_local void @bar4(
93+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
94+
; CHECK-NEXT: [[ENTRY:.*:]]
95+
; CHECK-NEXT: tail call void @bar2()
96+
; CHECK-NEXT: ret void
97+
;
98+
entry:
99+
tail call void @bar2()
100+
ret void
101+
}
102+
103+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
104+
define dso_local void @bar2() local_unnamed_addr #0 {
105+
; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
106+
; CHECK-LABEL: define dso_local void @bar2(
107+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
108+
; CHECK-NEXT: [[ENTRY:.*:]]
109+
; CHECK-NEXT: tail call void @bar3()
110+
; CHECK-NEXT: ret void
111+
;
112+
entry:
113+
tail call void @bar3()
114+
ret void
115+
}
116+
117+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
118+
define dso_local void @bar3() local_unnamed_addr #0 {
119+
; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
120+
; CHECK-LABEL: define dso_local void @bar3(
121+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
122+
; CHECK-NEXT: [[ENTRY:.*:]]
123+
; CHECK-NEXT: tail call void @bar4()
124+
; CHECK-NEXT: ret void
125+
;
126+
entry:
127+
tail call void @bar4()
128+
ret void
129+
}
130+
131+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
132+
define dso_local void @fun() local_unnamed_addr #0 {
133+
; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
134+
; CHECK-LABEL: define dso_local void @fun(
135+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
136+
; CHECK-NEXT: [[ENTRY:.*:]]
137+
; CHECK-NEXT: tail call void @baz()
138+
; CHECK-NEXT: ret void
139+
;
140+
entry:
141+
tail call void @baz()
142+
ret void
143+
}
144+
145+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
146+
define dso_local void @baz() local_unnamed_addr #0 {
147+
; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
148+
; CHECK-LABEL: define dso_local void @baz(
149+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR0]] {
150+
; CHECK-NEXT: [[ENTRY:.*:]]
151+
; CHECK-NEXT: tail call void @fun()
152+
; CHECK-NEXT: ret void
153+
;
154+
entry:
155+
tail call void @fun()
156+
ret void
157+
}
158+
159+
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" }
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --version 5
2+
; RUN: opt < %s -passes=function-attrs -S | FileCheck %s
3+
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"
4+
target triple = "aarch64-unknown-linux-gnu"
5+
6+
; This test includes a call graph with multiple SCCs. The purpose of this is
7+
; to check that norecurse is added to a function which calls a function which
8+
; is indirectly recursive but is not part of the recursive chain.
9+
; There are two SCCs in this test:
10+
; SCC#1: bar2, bar3, bar4
11+
; SCC#2: baz, fun
12+
; main() calls bar2 and baz, both of which are part of some indirect recursive
13+
; chain. but does not call back main() and hence main() can be marked as
14+
; norecurse.
15+
16+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
17+
define dso_local noundef i32 @main() local_unnamed_addr #0 {
18+
; CHECK: Function Attrs: nofree noinline norecurse nosync nounwind memory(none) uwtable
19+
; CHECK-LABEL: define dso_local noundef i32 @main(
20+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] {
21+
; CHECK-NEXT: [[ENTRY:.*:]]
22+
; CHECK-NEXT: tail call void @bar2()
23+
; CHECK-NEXT: tail call void @baz()
24+
; CHECK-NEXT: ret i32 0
25+
;
26+
entry:
27+
tail call void @bar2()
28+
tail call void @baz()
29+
ret i32 0
30+
}
31+
32+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
33+
define dso_local void @bar4() local_unnamed_addr #0 {
34+
; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
35+
; CHECK-LABEL: define dso_local void @bar4(
36+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR1:[0-9]+]] {
37+
; CHECK-NEXT: [[ENTRY:.*:]]
38+
; CHECK-NEXT: tail call void @bar2()
39+
; CHECK-NEXT: ret void
40+
;
41+
entry:
42+
tail call void @bar2()
43+
ret void
44+
}
45+
46+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
47+
define dso_local void @bar2() local_unnamed_addr #0 {
48+
; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
49+
; CHECK-LABEL: define dso_local void @bar2(
50+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR1]] {
51+
; CHECK-NEXT: [[ENTRY:.*:]]
52+
; CHECK-NEXT: tail call void @bar3()
53+
; CHECK-NEXT: ret void
54+
;
55+
entry:
56+
tail call void @bar3()
57+
ret void
58+
}
59+
60+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
61+
define dso_local void @bar3() local_unnamed_addr #0 {
62+
; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
63+
; CHECK-LABEL: define dso_local void @bar3(
64+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR1]] {
65+
; CHECK-NEXT: [[ENTRY:.*:]]
66+
; CHECK-NEXT: tail call void @bar4()
67+
; CHECK-NEXT: ret void
68+
;
69+
entry:
70+
tail call void @bar4()
71+
ret void
72+
}
73+
74+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
75+
define dso_local void @fun() local_unnamed_addr #0 {
76+
; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
77+
; CHECK-LABEL: define dso_local void @fun(
78+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR1]] {
79+
; CHECK-NEXT: [[ENTRY:.*:]]
80+
; CHECK-NEXT: tail call void @baz()
81+
; CHECK-NEXT: ret void
82+
;
83+
entry:
84+
tail call void @baz()
85+
ret void
86+
}
87+
88+
; Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
89+
define dso_local void @baz() local_unnamed_addr #0 {
90+
; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) uwtable
91+
; CHECK-LABEL: define dso_local void @baz(
92+
; CHECK-SAME: ) local_unnamed_addr #[[ATTR1]] {
93+
; CHECK-NEXT: [[ENTRY:.*:]]
94+
; CHECK-NEXT: tail call void @fun()
95+
; CHECK-NEXT: ret void
96+
;
97+
entry:
98+
tail call void @fun()
99+
ret void
100+
}
101+
102+
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" }

0 commit comments

Comments
 (0)