Skip to content

Commit 074125a

Browse files
committed
[IR] Relax convergence requirements on call
Before this commit, having a convergence token on a non-convergent call was considered to be an error. This commit relaxes this requirement and allows convergence tokens to be present on non-convergent calls. When such token is present, they have no effect as the underlying call is non-convergent. This allows passes like DCE to strip `convergent` attribute from functions for which all convergent operations have been stripped. When this happens, a convergence token can still exist in the call-site, causing the verifier to complain. Alternatives have been considered in llvm#134863 and llvm#134844.
1 parent 9a6c001 commit 074125a

File tree

5 files changed

+184
-4
lines changed

5 files changed

+184
-4
lines changed

llvm/include/llvm/IR/GenericConvergenceVerifierImpl.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,6 @@ void GenericConvergenceVerifier<ContextT>::visit(const InstructionT &I) {
102102
SeenFirstConvOp = true;
103103

104104
if (TokenDef || ConvOp != CONV_NONE) {
105-
Check(isConvergent(I),
106-
"Convergence control token can only be used in a convergent call.",
107-
{Context.print(&I)});
108105
Check(ConvergenceKind != UncontrolledConvergence,
109106
"Cannot mix controlled and uncontrolled convergence in the same "
110107
"function.",
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
2+
; RUN: opt %s -passes=adce -S | FileCheck %s
3+
4+
; CHECK: Function Attrs: convergent
5+
define i32 @foo(i32 %a) #0 {
6+
; CHECK-LABEL: define i32 @foo(
7+
; CHECK-SAME: i32 [[A:%.*]]) #[[ATTR0:[0-9]+]] {
8+
; CHECK-NEXT: [[ENTRY:.*:]]
9+
; CHECK-NEXT: ret i32 [[A]]
10+
;
11+
entry:
12+
%tk = call token @llvm.experimental.convergence.entry()
13+
ret i32 %a
14+
}
15+
16+
; CHECK: Function Attrs: convergent
17+
define void @bar() #0 {
18+
; CHECK-LABEL: define void @bar(
19+
; CHECK-SAME: ) #[[ATTR0]] {
20+
; CHECK-NEXT: [[ENTRY:.*:]]
21+
; CHECK-NEXT: ret void
22+
;
23+
entry:
24+
%tk = call token @llvm.experimental.convergence.anchor()
25+
ret void
26+
}
27+
28+
; CHECK: Function Attrs: convergent
29+
define void @baz() #0 {
30+
; CHECK-LABEL: define void @baz(
31+
; CHECK-SAME: ) #[[ATTR0]] {
32+
; CHECK-NEXT: [[ENTRY:.*:]]
33+
; CHECK-NEXT: br label %[[HEADER:.*]]
34+
; CHECK: [[HEADER]]:
35+
; CHECK-NEXT: br i1 true, label %[[BODY:.*]], label %[[EXIT:.*]]
36+
; CHECK: [[BODY]]:
37+
; CHECK-NEXT: br label %[[HEADER]]
38+
; CHECK: [[EXIT]]:
39+
; CHECK-NEXT: ret void
40+
;
41+
entry:
42+
%tk0 = call token @llvm.experimental.convergence.entry()
43+
br label %header
44+
45+
header:
46+
%tk1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %tk0) ]
47+
br i1 true, label %body, label %exit
48+
49+
body:
50+
br label %header
51+
52+
exit:
53+
ret void
54+
}
55+
56+
define void @indirect_inner() #0 {
57+
; CHECK-LABEL: define void @indirect_inner(
58+
; CHECK-SAME: ) #[[ATTR0]] {
59+
; CHECK-NEXT: [[ENTRY:.*:]]
60+
; CHECK-NEXT: ret void
61+
;
62+
entry:
63+
%tk0 = call token @llvm.experimental.convergence.entry()
64+
ret void
65+
}
66+
67+
define void @indirect() #0 {
68+
; CHECK-LABEL: define void @indirect(
69+
; CHECK-SAME: ) #[[ATTR0]] {
70+
; CHECK-NEXT: [[ENTRY:.*:]]
71+
; CHECK-NEXT: [[TK0:%.*]] = call token @llvm.experimental.convergence.entry()
72+
; CHECK-NEXT: [[VAR:%.*]] = alloca ptr, align 8
73+
; CHECK-NEXT: store ptr @indirect_inner, ptr [[VAR]], align 8
74+
; CHECK-NEXT: [[PTR:%.*]] = load ptr, ptr [[VAR]], align 8
75+
; CHECK-NEXT: call void [[PTR]]() #[[ATTR0]] [ "convergencectrl"(token [[TK0]]) ]
76+
; CHECK-NEXT: ret void
77+
;
78+
entry:
79+
%tk0 = call token @llvm.experimental.convergence.entry()
80+
%var = alloca ptr, align 8
81+
store ptr @indirect_inner, ptr %var, align 8
82+
%ptr = load ptr, ptr %var, align 8
83+
call void %ptr() convergent [ "convergencectrl"(token %tk0) ]
84+
ret void
85+
}
86+
87+
declare token @llvm.experimental.convergence.entry() #1
88+
declare token @llvm.experimental.convergence.anchor() #1
89+
declare token @llvm.experimental.convergence.loop() #1
90+
91+
attributes #0 = { convergent }
92+
attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
2+
; RUN: opt %s -passes=bdce -S | FileCheck %s
3+
4+
; CHECK: Function Attrs: convergent
5+
define i32 @foo(i32 %a) #0 {
6+
; CHECK-LABEL: define i32 @foo(
7+
; CHECK-SAME: i32 [[A:%.*]]) #[[ATTR0:[0-9]+]] {
8+
; CHECK-NEXT: [[ENTRY:.*:]]
9+
; CHECK-NEXT: ret i32 [[A]]
10+
;
11+
entry:
12+
%tk0 = call token @llvm.experimental.convergence.entry()
13+
ret i32 %a
14+
}
15+
16+
; CHECK: Function Attrs: convergent
17+
define void @bar() #0 {
18+
; CHECK-LABEL: define void @bar(
19+
; CHECK-SAME: ) #[[ATTR0]] {
20+
; CHECK-NEXT: [[ENTRY:.*:]]
21+
; CHECK-NEXT: ret void
22+
;
23+
entry:
24+
%tk0 = call token @llvm.experimental.convergence.anchor()
25+
ret void
26+
}
27+
28+
; CHECK: Function Attrs: convergent
29+
define void @baz() #0 {
30+
; CHECK-LABEL: define void @baz(
31+
; CHECK-SAME: ) #[[ATTR0]] {
32+
; CHECK-NEXT: [[ENTRY:.*:]]
33+
; CHECK-NEXT: br label %[[HEADER:.*]]
34+
; CHECK: [[HEADER]]:
35+
; CHECK-NEXT: br i1 true, label %[[BODY:.*]], label %[[EXIT:.*]]
36+
; CHECK: [[BODY]]:
37+
; CHECK-NEXT: br label %[[HEADER]]
38+
; CHECK: [[EXIT]]:
39+
; CHECK-NEXT: ret void
40+
;
41+
entry:
42+
%tk0 = call token @llvm.experimental.convergence.entry()
43+
br label %header
44+
45+
header:
46+
%tk1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %tk0) ]
47+
br i1 true, label %body, label %exit
48+
49+
body:
50+
br label %header
51+
52+
exit:
53+
ret void
54+
}
55+
56+
declare token @llvm.experimental.convergence.entry() #1
57+
declare token @llvm.experimental.convergence.anchor() #1
58+
declare token @llvm.experimental.convergence.loop() #1
59+
60+
attributes #0 = { convergent }
61+
attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }
62+

llvm/test/Transforms/FunctionAttrs/convergent.ll

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,32 @@ define i32 @noopt_friend() convergent {
129129
%a = call i32 @noopt()
130130
ret i32 0
131131
}
132+
133+
134+
; A function which is stripped of its convergent attribute, even
135+
; if used in a controlled convergence call.
136+
; This should be OK.
137+
define i32 @leaf_noconvergent_used() convergent {
138+
; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
139+
; CHECK-LABEL: define {{[^@]+}}@leaf_noconvergent_used
140+
; CHECK-SAME: () #[[ATTR0]] {
141+
; CHECK-NEXT: ret i32 0
142+
;
143+
ret i32 0
144+
}
145+
146+
define i32 @nonleaf_convergent() convergent {
147+
; CHECK: Function Attrs: convergent mustprogress nofree norecurse nosync nounwind willreturn memory(none)
148+
; CHECK-LABEL: define {{[^@]+}}@nonleaf_convergent
149+
; CHECK-SAME: () #[[ATTR7:[0-9]+]] {
150+
; CHECK-NEXT: [[TMP1:%.*]] = call token @llvm.experimental.convergence.entry()
151+
; CHECK-NEXT: [[TMP2:%.*]] = call i32 @leaf_noconvergent_used() [ "convergencectrl"(token [[TMP1]]) ]
152+
; CHECK-NEXT: ret i32 0
153+
;
154+
%1 = call token @llvm.experimental.convergence.entry()
155+
%2 = call i32 @leaf_noconvergent_used() [ "convergencectrl"(token %1) ]
156+
ret i32 0
157+
}
158+
159+
160+
declare token @llvm.experimental.convergence.entry() #1

llvm/test/Verifier/convergencectrl-invalid.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ define void @wrong_token() {
2020
ret void
2121
}
2222

23-
; CHECK: Convergence control token can only be used in a convergent call.
23+
; convergence control token can be used on non-convergent calls, but it has no effect.
2424
; CHECK-NEXT call void @g(){{.*}}%t05_tok1
2525
define void @missing.attribute() {
2626
%t05_tok1 = call token @llvm.experimental.convergence.anchor()

0 commit comments

Comments
 (0)