Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion llvm/lib/Transforms/IPO/FunctionAttrs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1864,6 +1864,19 @@ static bool InstrBreaksNonConvergent(Instruction &I,
!SCCNodes.contains(CB->getCalledFunction());
}

static bool FunctionRequiresConvergence(const Function &F) {
for (auto &Use : F.uses()) {
CallBase *CB = dyn_cast<CallBase>(Use.getUser());
if (!CB)
return true;

if (CB->getConvergenceControlToken())
return true;
}

return false;
}

/// Helper for NoUnwind inference predicate InstrBreaksAttribute.
static bool InstrBreaksNonThrowing(Instruction &I, const SCCNodeSet &SCCNodes) {
if (!I.mayThrow(/* IncludePhaseOneUnwind */ true))
Expand Down Expand Up @@ -1967,7 +1980,9 @@ static void inferConvergent(const SCCNodeSet &SCCNodes,
AI.registerAttrInference(AttributeInferer::InferenceDescriptor{
Attribute::Convergent,
// Skip non-convergent functions.
[](const Function &F) { return !F.isConvergent(); },
[](const Function &F) {
return !F.isConvergent() || FunctionRequiresConvergence(F);
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does FunctionAttrs run in a bottom up and top down phase? Ideally we wouldn't need to inspect the users of the function, and strip convergent from call sites when possible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the pass is running bottom-up, so if we drop convergence intrinsics in the callee, then we should technically be able to strip the control tokens in the caller. But this would go beyond the scope of this pass no?

// Instructions that break non-convergent assumption.
[SCCNodes](Instruction &I) {
return InstrBreaksNonConvergent(I, SCCNodes);
Expand Down
46 changes: 46 additions & 0 deletions llvm/test/Transforms/ADCE/convergence.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
; RUN: opt %s -passes=adce -S | FileCheck %s

; CHECK: Function Attrs: convergent
; CHECK-NEXT: define i32 @foo(i32 %a) #0 {
define i32 @foo(i32 %a) #0 {
entry:
; CHECK-NOT: %0 = call token @llvm.experimental.convergence.entry()
%0 = call token @llvm.experimental.convergence.entry()
ret i32 %a
}

; CHECK: Function Attrs: convergent
; CHECK-NEXT: define void @bar() #0 {
define void @bar() #0 {
entry:
; CHECK-NOT: %0 = call token @llvm.experimental.convergence.entry()
%0 = call token @llvm.experimental.convergence.anchor()
ret void
}

; CHECK: Function Attrs: convergent
; CHECK-NEXT: define void @baz() #0 {
define void @baz() #0 {
entry:
; CHECK-NOT: %0 = call token @llvm.experimental.convergence.entry()
%0 = call token @llvm.experimental.convergence.entry()
br label %header

header:
; CHECK-NOT: %1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %0) ]
%1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %0) ]
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) }
47 changes: 47 additions & 0 deletions llvm/test/Transforms/BDCE/convergence.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
; RUN: opt %s -passes=bdce -S | FileCheck %s

; CHECK: Function Attrs: convergent
; CHECK-NEXT: define i32 @foo(i32 %a) #0 {
define i32 @foo(i32 %a) #0 {
entry:
; CHECK-NOT: %0 = call token @llvm.experimental.convergence.entry()
%0 = call token @llvm.experimental.convergence.entry()
ret i32 %a
}

; CHECK: Function Attrs: convergent
; CHECK-NEXT: define void @bar() #0 {
define void @bar() #0 {
entry:
; CHECK-NOT: %0 = call token @llvm.experimental.convergence.entry()
%0 = call token @llvm.experimental.convergence.anchor()
ret void
}

; CHECK: Function Attrs: convergent
; CHECK-NEXT: define void @baz() #0 {
define void @baz() #0 {
entry:
; CHECK-NOT: %0 = call token @llvm.experimental.convergence.entry()
%0 = call token @llvm.experimental.convergence.entry()
br label %header

header:
; CHECK-NOT: %1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %0) ]
%1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %0) ]
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) }

32 changes: 32 additions & 0 deletions llvm/test/Transforms/FunctionAttrs/convergent.ll
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,35 @@ define i32 @noopt_friend() convergent {
%a = call i32 @noopt()
ret i32 0
}

; A function which should normally be stripped of its convergent attribute,
; but because it's used in a controlled convergence call, the attribute
; remains.
; This could be improved by propagating the non-convergence outside of the
; function, but for the time being, we stop when we encounter this scenario.
define i32 @leaf_noconvergent_used() convergent {
; CHECK: Function Attrs: convergent mustprogress nofree norecurse nosync nounwind willreturn memory(none)
; CHECK-LABEL: define {{[^@]+}}@leaf_noconvergent_used
; CHECK-SAME: () #[[ATTR7:[0-9]+]] {
; 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]] {
; 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

attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }