Skip to content

Commit b7021ef

Browse files
committed
[IPO] Prevent removal of some convergent attr
When a callee is marked as `convergent`, some targets like HLSL/SPIR-V add a convergent token to the call. This is valid if both functions are marked as `convergent`. ADCE/BDCE and other DCE passes were allowed to remove convergence intrinsics when their token were unused. This meant a leaf function could lose all its convergence intrinsics. This would allow further optimization to remove the `convergent` attribute from the callee. Issue was the caller was not updated, and we now had a convergence token attached to a call function calling a non-convergent function. This commit limits the removal of the `convergent` attribute when only users are calls without convergence intrinsics. If any other use is found (either a call to a convergent, or something else like taking the function address), the removal is prevented.
1 parent dae0ef5 commit b7021ef

File tree

4 files changed

+141
-1
lines changed

4 files changed

+141
-1
lines changed

llvm/lib/Transforms/IPO/FunctionAttrs.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1864,6 +1864,19 @@ static bool InstrBreaksNonConvergent(Instruction &I,
18641864
!SCCNodes.contains(CB->getCalledFunction());
18651865
}
18661866

1867+
static bool FunctionRequiresConvergence(const Function &F) {
1868+
for (auto &Use : F.uses()) {
1869+
CallBase *CB = dyn_cast<CallBase>(Use.getUser());
1870+
if (!CB)
1871+
return true;
1872+
1873+
if (CB->getConvergenceControlToken())
1874+
return true;
1875+
}
1876+
1877+
return false;
1878+
}
1879+
18671880
/// Helper for NoUnwind inference predicate InstrBreaksAttribute.
18681881
static bool InstrBreaksNonThrowing(Instruction &I, const SCCNodeSet &SCCNodes) {
18691882
if (!I.mayThrow(/* IncludePhaseOneUnwind */ true))
@@ -1967,7 +1980,9 @@ static void inferConvergent(const SCCNodeSet &SCCNodes,
19671980
AI.registerAttrInference(AttributeInferer::InferenceDescriptor{
19681981
Attribute::Convergent,
19691982
// Skip non-convergent functions.
1970-
[](const Function &F) { return !F.isConvergent(); },
1983+
[](const Function &F) {
1984+
return !F.isConvergent() || FunctionRequiresConvergence(F);
1985+
},
19711986
// Instructions that break non-convergent assumption.
19721987
[SCCNodes](Instruction &I) {
19731988
return InstrBreaksNonConvergent(I, SCCNodes);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
; RUN: opt %s -passes=adce -S | FileCheck %s
2+
3+
; CHECK: Function Attrs: convergent
4+
; CHECK-NEXT: define i32 @foo(i32 %a) #0 {
5+
define i32 @foo(i32 %a) #0 {
6+
entry:
7+
; CHECK-NOT: %0 = call token @llvm.experimental.convergence.entry()
8+
%0 = call token @llvm.experimental.convergence.entry()
9+
ret i32 %a
10+
}
11+
12+
; CHECK: Function Attrs: convergent
13+
; CHECK-NEXT: define void @bar() #0 {
14+
define void @bar() #0 {
15+
entry:
16+
; CHECK-NOT: %0 = call token @llvm.experimental.convergence.entry()
17+
%0 = call token @llvm.experimental.convergence.anchor()
18+
ret void
19+
}
20+
21+
; CHECK: Function Attrs: convergent
22+
; CHECK-NEXT: define void @baz() #0 {
23+
define void @baz() #0 {
24+
entry:
25+
; CHECK-NOT: %0 = call token @llvm.experimental.convergence.entry()
26+
%0 = call token @llvm.experimental.convergence.entry()
27+
br label %header
28+
29+
header:
30+
; CHECK-NOT: %1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %0) ]
31+
%1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %0) ]
32+
br i1 true, label %body, label %exit
33+
34+
body:
35+
br label %header
36+
37+
exit:
38+
ret void
39+
}
40+
41+
declare token @llvm.experimental.convergence.entry() #1
42+
declare token @llvm.experimental.convergence.anchor() #1
43+
declare token @llvm.experimental.convergence.loop() #1
44+
45+
attributes #0 = { convergent }
46+
attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
; RUN: opt %s -passes=bdce -S | FileCheck %s
2+
3+
; CHECK: Function Attrs: convergent
4+
; CHECK-NEXT: define i32 @foo(i32 %a) #0 {
5+
define i32 @foo(i32 %a) #0 {
6+
entry:
7+
; CHECK-NOT: %0 = call token @llvm.experimental.convergence.entry()
8+
%0 = call token @llvm.experimental.convergence.entry()
9+
ret i32 %a
10+
}
11+
12+
; CHECK: Function Attrs: convergent
13+
; CHECK-NEXT: define void @bar() #0 {
14+
define void @bar() #0 {
15+
entry:
16+
; CHECK-NOT: %0 = call token @llvm.experimental.convergence.entry()
17+
%0 = call token @llvm.experimental.convergence.anchor()
18+
ret void
19+
}
20+
21+
; CHECK: Function Attrs: convergent
22+
; CHECK-NEXT: define void @baz() #0 {
23+
define void @baz() #0 {
24+
entry:
25+
; CHECK-NOT: %0 = call token @llvm.experimental.convergence.entry()
26+
%0 = call token @llvm.experimental.convergence.entry()
27+
br label %header
28+
29+
header:
30+
; CHECK-NOT: %1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %0) ]
31+
%1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %0) ]
32+
br i1 true, label %body, label %exit
33+
34+
body:
35+
br label %header
36+
37+
exit:
38+
ret void
39+
}
40+
41+
declare token @llvm.experimental.convergence.entry() #1
42+
declare token @llvm.experimental.convergence.anchor() #1
43+
declare token @llvm.experimental.convergence.loop() #1
44+
45+
attributes #0 = { convergent }
46+
attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }
47+

llvm/test/Transforms/FunctionAttrs/convergent.ll

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,35 @@ define i32 @noopt_friend() convergent {
129129
%a = call i32 @noopt()
130130
ret i32 0
131131
}
132+
133+
; A function which should normally be stripped of its convergent attribute,
134+
; but because it's used in a controlled convergence call, the attribute
135+
; remains.
136+
; This could be improved by propagating the non-convergence outside of the
137+
; function, but for the time being, we stop when we encounter this scenario.
138+
define i32 @leaf_noconvergent_used() convergent {
139+
; CHECK: Function Attrs: convergent mustprogress nofree norecurse nosync nounwind willreturn memory(none)
140+
; CHECK-LABEL: define {{[^@]+}}@leaf_noconvergent_used
141+
; CHECK-SAME: () #[[ATTR7:[0-9]+]] {
142+
; CHECK-NEXT: ret i32 0
143+
;
144+
ret i32 0
145+
}
146+
147+
define i32 @nonleaf_convergent() convergent {
148+
; CHECK: Function Attrs: convergent mustprogress nofree norecurse nosync nounwind willreturn memory(none)
149+
; CHECK-LABEL: define {{[^@]+}}@nonleaf_convergent
150+
; CHECK-SAME: () #[[ATTR7]] {
151+
; CHECK-NEXT: [[TMP1:%.*]] = call token @llvm.experimental.convergence.entry()
152+
; CHECK-NEXT: [[TMP2:%.*]] = call i32 @leaf_noconvergent_used() [ "convergencectrl"(token [[TMP1]]) ]
153+
; CHECK-NEXT: ret i32 0
154+
;
155+
%1 = call token @llvm.experimental.convergence.entry()
156+
%2 = call i32 @leaf_noconvergent_used() [ "convergencectrl"(token %1) ]
157+
ret i32 0
158+
}
159+
160+
161+
declare token @llvm.experimental.convergence.entry() #1
162+
163+
attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }

0 commit comments

Comments
 (0)