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;
if (!ExternalNode) {
Changed |= addNoAliasAttrs(SCCNodes);
Changed |= addNonNullAttrs(SCCNodes, *TLI);
+ Changed |= removeConvergentAttrs(SCC, SCCNodes);
}
Changed |= addNoRecurseAttrs(SCC);
--- /dev/null
+; 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
+}