summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--llvm/include/llvm/IR/Function.h3
-rw-r--r--llvm/lib/Transforms/IPO/FunctionAttrs.cpp63
-rw-r--r--llvm/test/Transforms/FunctionAttrs/convergent.ll94
3 files changed, 160 insertions, 0 deletions
diff --git a/llvm/include/llvm/IR/Function.h b/llvm/include/llvm/IR/Function.h
index 1ed200f0e18..c00f80fa3aa 100644
--- a/llvm/include/llvm/IR/Function.h
+++ b/llvm/include/llvm/IR/Function.h
@@ -339,6 +339,9 @@ public:
void setConvergent() {
addFnAttr(Attribute::Convergent);
}
+ void setNotConvergent() {
+ removeFnAttr(Attribute::Convergent);
+ }
/// Determine if the function is known not to recurse, directly or
/// indirectly.
diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index 60cc874d4d4..2445b7f3c8a 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -935,6 +935,68 @@ static bool addNonNullAttrs(const SCCNodeSet &SCCNodes,
return MadeChange;
}
+/// Removes convergent attributes where we can prove that none of the SCC's
+/// callees are themselves convergent. Returns true if successful at removing
+/// the attribute.
+static bool removeConvergentAttrs(const CallGraphSCC &SCC,
+ const SCCNodeSet &SCCNodes) {
+ // Determines whether a function can be made non-convergent, ignoring all
+ // other functions in SCC. (A function can *actually* be made non-convergent
+ // only if all functions in its SCC can be made convergent.)
+ auto CanRemoveConvergent = [&] (CallGraphNode *CGN) {
+ Function *F = CGN->getFunction();
+ if (!F) return false;
+
+ if (!F->isConvergent()) return true;
+
+ // Can't remove convergent from declarations.
+ if (F->isDeclaration()) return false;
+
+ // Don't remove convergent from optnone functions.
+ if (F->hasFnAttribute(Attribute::OptimizeNone))
+ return false;
+
+ // Can't remove convergent if any of F's callees -- ignoring functions in the
+ // SCC itself -- are convergent.
+ if (llvm::any_of(*CGN, [&](const CallGraphNode::CallRecord &CR) {
+ Function *F = CR.second->getFunction();
+ return SCCNodes.count(F) == 0 && (!F || F->isConvergent());
+ }))
+ return false;
+
+ // CGN doesn't contain calls to intrinsics, so iterate over all of F's
+ // callsites, looking for any calls to convergent intrinsics. If we find one,
+ // F must remain marked as convergent.
+ auto IsConvergentIntrinsicCall = [](Instruction &I) {
+ CallSite CS(cast<Value>(&I));
+ if (!CS)
+ return false;
+ Function *Callee = CS.getCalledFunction();
+ return Callee && Callee->isIntrinsic() && Callee->isConvergent();
+ };
+ return !llvm::any_of(*F, [=](BasicBlock &BB) {
+ return llvm::any_of(BB, IsConvergentIntrinsicCall);
+ });
+ };
+
+ // We can remove the convergent attr from functions in the SCC if they all can
+ // be made non-convergent (because they call only non-convergent functions,
+ // other than each other).
+ if (!llvm::all_of(SCC, CanRemoveConvergent)) return false;
+
+ // If we got here, all of the SCC's callees are non-convergent, and none of
+ // the optnone functions in the SCC are marked as convergent. Therefore all
+ // of the SCC's functions can be marked as non-convergent.
+ for (CallGraphNode *CGN : SCC)
+ if (Function *F = CGN->getFunction()) {
+ if (F->isConvergent())
+ DEBUG(dbgs() << "Removing convergent attr from " << F->getName()
+ << "\n");
+ F->setNotConvergent();
+ }
+ return true;
+}
+
static bool setDoesNotRecurse(Function &F) {
if (F.doesNotRecurse())
return false;
@@ -1011,6 +1073,7 @@ bool PostOrderFunctionAttrs::runOnSCC(CallGraphSCC &SCC) {
if (!ExternalNode) {
Changed |= addNoAliasAttrs(SCCNodes);
Changed |= addNonNullAttrs(SCCNodes, *TLI);
+ Changed |= removeConvergentAttrs(SCC, SCCNodes);
}
Changed |= addNoRecurseAttrs(SCC);
diff --git a/llvm/test/Transforms/FunctionAttrs/convergent.ll b/llvm/test/Transforms/FunctionAttrs/convergent.ll
new file mode 100644
index 00000000000..46370d7bf30
--- /dev/null
+++ b/llvm/test/Transforms/FunctionAttrs/convergent.ll
@@ -0,0 +1,94 @@
+; RUN: opt < %s -basicaa -functionattrs -rpo-functionattrs -S | FileCheck %s
+
+; CHECK: Function Attrs
+; CHECK-NOT: convergent
+; CHECK-NEXT: define i32 @nonleaf()
+define i32 @nonleaf() convergent {
+ %a = call i32 @leaf()
+ ret i32 %a
+}
+
+; CHECK: Function Attrs
+; CHECK-NOT: convergent
+; CHECK-NEXT: define i32 @leaf()
+define i32 @leaf() convergent {
+ ret i32 0
+}
+
+; CHECK: Function Attrs
+; CHECK-SAME: convergent
+; CHECK-NEXT: declare i32 @k()
+declare i32 @k() convergent
+
+; CHECK: Function Attrs
+; CHECK-SAME: convergent
+; CHECK-NEXT: define i32 @extern()
+define i32 @extern() convergent {
+ %a = call i32 @k()
+ ret i32 %a
+}
+
+; CHECK: Function Attrs
+; CHECK-SAME: convergent
+; CHECK-NEXT: define i32 @call_extern()
+define i32 @call_extern() convergent {
+ %a = call i32 @extern()
+ ret i32 %a
+}
+
+; CHECK: Function Attrs
+; CHECK-SAME: convergent
+; CHECK-NEXT: declare void @llvm.cuda.syncthreads()
+declare void @llvm.cuda.syncthreads() convergent
+
+; CHECK: Function Attrs
+; CHECK-SAME: convergent
+; CHECK-NEXT: define i32 @intrinsic()
+define i32 @intrinsic() convergent {
+ call void @llvm.cuda.syncthreads()
+ ret i32 0
+}
+
+@xyz = global i32 ()* null
+; CHECK: Function Attrs
+; CHECK-SAME: convergent
+; CHECK-NEXT: define i32 @functionptr()
+define i32 @functionptr() convergent {
+ %1 = load i32 ()*, i32 ()** @xyz
+ %2 = call i32 %1()
+ ret i32 %2
+}
+
+; CHECK: Function Attrs
+; CHECK-NOT: convergent
+; CHECK-NEXT: define i32 @recursive1()
+define i32 @recursive1() convergent {
+ %a = call i32 @recursive2()
+ ret i32 %a
+}
+
+; CHECK: Function Attrs
+; CHECK-NOT: convergent
+; CHECK-NEXT: define i32 @recursive2()
+define i32 @recursive2() convergent {
+ %a = call i32 @recursive1()
+ ret i32 %a
+}
+
+; CHECK: Function Attrs
+; CHECK-SAME: convergent
+; CHECK-NEXT: define i32 @noopt()
+define i32 @noopt() convergent optnone noinline {
+ %a = call i32 @noopt_friend()
+ ret i32 0
+}
+
+; A function which is mutually-recursive with a convergent, optnone function
+; shouldn't have its convergent attribute stripped.
+; CHECK: Function Attrs
+; CHECK-SAME: convergent
+; CHECK-NEXT: define i32 @noopt_friend()
+define i32 @noopt_friend() convergent {
+ %a = call i32 @noopt()
+ ret i32 0
+}
OpenPOWER on IntegriCloud