diff --git a/llvm/include/llvm/IR/GenericConvergenceVerifierImpl.h b/llvm/include/llvm/IR/GenericConvergenceVerifierImpl.h index e076ac4a14162..ddcce17b77c8b 100644 --- a/llvm/include/llvm/IR/GenericConvergenceVerifierImpl.h +++ b/llvm/include/llvm/IR/GenericConvergenceVerifierImpl.h @@ -102,9 +102,6 @@ void GenericConvergenceVerifier::visit(const InstructionT &I) { SeenFirstConvOp = true; if (TokenDef || ConvOp != CONV_NONE) { - Check(isConvergent(I), - "Convergence control token can only be used in a convergent call.", - {Context.print(&I)}); Check(ConvergenceKind != UncontrolledConvergence, "Cannot mix controlled and uncontrolled convergence in the same " "function.", diff --git a/llvm/test/Assembler/convergence-control.ll b/llvm/test/Assembler/convergence-control.ll index f32f4a9f53707..c7dd04cf5c25b 100644 --- a/llvm/test/Assembler/convergence-control.ll +++ b/llvm/test/Assembler/convergence-control.ll @@ -24,6 +24,13 @@ define void @mixed2() { ret void } +; convergence control token can be used on non-convergent calls, +; but it has no effect. +define void @mixed3() { + %t05_tok1 = call token @llvm.experimental.convergence.anchor() + call void @g() [ "convergencectrl"(token %t05_tok1) ] + ret void +} define void @region_nesting1(i1 %arg) convergent { A: diff --git a/llvm/test/Transforms/ADCE/convergence.ll b/llvm/test/Transforms/ADCE/convergence.ll new file mode 100644 index 0000000000000..01a205ebda148 --- /dev/null +++ b/llvm/test/Transforms/ADCE/convergence.ll @@ -0,0 +1,93 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5 +; RUN: opt %s -passes=adce -S | FileCheck %s + +define i32 @foo(i32 %a) #0 { +; CHECK-LABEL: define i32 @foo( +; CHECK-SAME: i32 [[A:%.*]]) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: ret i32 [[A]] +; +entry: + %tk = call token @llvm.experimental.convergence.entry() + ret i32 %a +} + +define void @bar() #0 { +; CHECK-LABEL: define void @bar( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: ret void +; +entry: + %tk = call token @llvm.experimental.convergence.anchor() + ret void +} + +define void @baz() #0 { +; CHECK-LABEL: define void @baz( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: br label %[[HEADER:.*]] +; CHECK: [[HEADER]]: +; CHECK-NEXT: br i1 true, label %[[BODY:.*]], label %[[EXIT:.*]] +; CHECK: [[BODY]]: +; CHECK-NEXT: br label %[[HEADER]] +; CHECK: [[EXIT]]: +; CHECK-NEXT: ret void +; +entry: + %tk0 = call token @llvm.experimental.convergence.entry() + br label %header + +header: + %tk1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %tk0) ] + br i1 true, label %body, label %exit + +body: + br label %header + +exit: + ret void +} + +define void @indirect_inner() #0 { +; CHECK-LABEL: define void @indirect_inner( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: ret void +; +entry: + %tk0 = call token @llvm.experimental.convergence.entry() + ret void +} + +define void @indirect() #0 { +; CHECK-LABEL: define void @indirect( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[TK0:%.*]] = call token @llvm.experimental.convergence.entry() +; CHECK-NEXT: [[VAR:%.*]] = alloca ptr, align 8 +; CHECK-NEXT: store ptr @indirect_inner, ptr [[VAR]], align 8 +; CHECK-NEXT: [[PTR:%.*]] = load ptr, ptr [[VAR]], align 8 +; CHECK-NEXT: call void [[PTR]]() #[[ATTR0]] [ "convergencectrl"(token [[TK0]]) ] +; CHECK-NEXT: ret void +; +entry: + %tk0 = call token @llvm.experimental.convergence.entry() + %var = alloca ptr, align 8 + store ptr @indirect_inner, ptr %var, align 8 + %ptr = load ptr, ptr %var, align 8 + call void %ptr() convergent [ "convergencectrl"(token %tk0) ] + ret void +} + +declare token @llvm.experimental.convergence.entry() #1 +declare token @llvm.experimental.convergence.anchor() #1 +declare token @llvm.experimental.convergence.loop() #1 + +attributes #0 = { convergent } +attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) } +;. +; CHECK: attributes #[[ATTR0]] = { convergent } +; CHECK: attributes #[[ATTR1:[0-9]+]] = { convergent nocallback nofree nosync nounwind willreturn memory(none) } +;. diff --git a/llvm/test/Transforms/BDCE/convergence.ll b/llvm/test/Transforms/BDCE/convergence.ll new file mode 100644 index 0000000000000..5bebe37acc845 --- /dev/null +++ b/llvm/test/Transforms/BDCE/convergence.ll @@ -0,0 +1,62 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5 +; RUN: opt %s -passes=bdce -S | FileCheck %s + +define i32 @foo(i32 %a) #0 { +; CHECK-LABEL: define i32 @foo( +; CHECK-SAME: i32 [[A:%.*]]) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: ret i32 [[A]] +; +entry: + %tk0 = call token @llvm.experimental.convergence.entry() + ret i32 %a +} + +define void @bar() #0 { +; CHECK-LABEL: define void @bar( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: ret void +; +entry: + %tk0 = call token @llvm.experimental.convergence.anchor() + ret void +} + +define void @baz() #0 { +; CHECK-LABEL: define void @baz( +; CHECK-SAME: ) #[[ATTR0]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: br label %[[HEADER:.*]] +; CHECK: [[HEADER]]: +; CHECK-NEXT: br i1 true, label %[[BODY:.*]], label %[[EXIT:.*]] +; CHECK: [[BODY]]: +; CHECK-NEXT: br label %[[HEADER]] +; CHECK: [[EXIT]]: +; CHECK-NEXT: ret void +; +entry: + %tk0 = call token @llvm.experimental.convergence.entry() + br label %header + +header: + %tk1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %tk0) ] + br i1 true, label %body, label %exit + +body: + br label %header + +exit: + ret void +} + +declare token @llvm.experimental.convergence.entry() #1 +declare token @llvm.experimental.convergence.anchor() #1 +declare token @llvm.experimental.convergence.loop() #1 + +attributes #0 = { convergent } +attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) } +;. +; CHECK: attributes #[[ATTR0]] = { convergent } +; CHECK: attributes #[[ATTR1:[0-9]+]] = { convergent nocallback nofree nosync nounwind willreturn memory(none) } +;. diff --git a/llvm/test/Transforms/FunctionAttrs/convergent.ll b/llvm/test/Transforms/FunctionAttrs/convergent.ll index fe8029d39d924..49c357bd6bc86 100644 --- a/llvm/test/Transforms/FunctionAttrs/convergent.ll +++ b/llvm/test/Transforms/FunctionAttrs/convergent.ll @@ -1,4 +1,4 @@ -; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals ; RUN: opt -passes=function-attrs -S < %s | FileCheck %s define i32 @nonleaf() convergent { @@ -129,3 +129,43 @@ define i32 @noopt_friend() convergent { %a = call i32 @noopt() ret i32 0 } + + +; A function which is stripped of its convergent attribute, even +; if used in a controlled convergence call. +; This should be OK. +define i32 @leaf_noconvergent_used() convergent { +; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) +; CHECK-LABEL: define {{[^@]+}}@leaf_noconvergent_used +; CHECK-SAME: () #[[ATTR0]] { +; CHECK-NEXT: ret i32 0 +; + ret i32 0 +} + +define i32 @nonleaf_convergent() convergent { +; CHECK: Function Attrs: convergent mustprogress nofree norecurse nosync nounwind willreturn memory(none) +; CHECK-LABEL: define {{[^@]+}}@nonleaf_convergent +; CHECK-SAME: () #[[ATTR7:[0-9]+]] { +; CHECK-NEXT: [[TMP1:%.*]] = call token @llvm.experimental.convergence.entry() +; CHECK-NEXT: [[TMP2:%.*]] = call i32 @leaf_noconvergent_used() [ "convergencectrl"(token [[TMP1]]) ] +; CHECK-NEXT: ret i32 0 +; + %1 = call token @llvm.experimental.convergence.entry() + %2 = call i32 @leaf_noconvergent_used() [ "convergencectrl"(token %1) ] + ret i32 0 +} + + +declare token @llvm.experimental.convergence.entry() #1 +;. +; CHECK: attributes #[[ATTR0]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) } +; CHECK: attributes #[[ATTR1]] = { convergent } +; CHECK: attributes #[[ATTR2]] = { norecurse } +; CHECK: attributes #[[ATTR3:[0-9]+]] = { convergent nocallback nounwind } +; CHECK: attributes #[[ATTR4]] = { convergent norecurse nounwind } +; CHECK: attributes #[[ATTR5]] = { nofree nosync nounwind memory(none) } +; CHECK: attributes #[[ATTR6]] = { convergent noinline optnone } +; CHECK: attributes #[[ATTR7]] = { convergent mustprogress nofree norecurse nosync nounwind willreturn memory(none) } +; CHECK: attributes #[[ATTR8:[0-9]+]] = { convergent nocallback nofree nosync nounwind willreturn memory(none) } +;. diff --git a/llvm/test/Verifier/convergencectrl-invalid.ll b/llvm/test/Verifier/convergencectrl-invalid.ll index e1fffcd1c6033..907c3464ffce9 100644 --- a/llvm/test/Verifier/convergencectrl-invalid.ll +++ b/llvm/test/Verifier/convergencectrl-invalid.ll @@ -20,14 +20,6 @@ define void @wrong_token() { ret void } -; CHECK: Convergence control token can only be used in a convergent call. -; CHECK-NEXT call void @g(){{.*}}%t05_tok1 -define void @missing.attribute() { - %t05_tok1 = call token @llvm.experimental.convergence.anchor() - call void @g() [ "convergencectrl"(token %t05_tok1) ] - ret void -} - ; CHECK: The 'convergencectrl' bundle requires exactly one token use. ; CHECK-NEXT: call void @g() define void @multiple_tokens() {