Index: llvm/trunk/include/llvm/IR/Function.h =================================================================== --- llvm/trunk/include/llvm/IR/Function.h +++ llvm/trunk/include/llvm/IR/Function.h @@ -339,6 +339,9 @@ void setConvergent() { addFnAttr(Attribute::Convergent); } + void setNotConvergent() { + removeFnAttr(Attribute::Convergent); + } /// Determine if the function is known not to recurse, directly or /// indirectly. Index: llvm/trunk/lib/Transforms/IPO/FunctionAttrs.cpp =================================================================== --- llvm/trunk/lib/Transforms/IPO/FunctionAttrs.cpp +++ llvm/trunk/lib/Transforms/IPO/FunctionAttrs.cpp @@ -935,6 +935,68 @@ 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(&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 @@ if (!ExternalNode) { Changed |= addNoAliasAttrs(SCCNodes); Changed |= addNonNullAttrs(SCCNodes, *TLI); + Changed |= removeConvergentAttrs(SCC, SCCNodes); } Changed |= addNoRecurseAttrs(SCC); Index: llvm/trunk/test/Transforms/FunctionAttrs/convergent.ll =================================================================== --- llvm/trunk/test/Transforms/FunctionAttrs/convergent.ll +++ llvm/trunk/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 +}