Index: include/llvm/IR/Function.h =================================================================== --- include/llvm/IR/Function.h +++ 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: lib/Transforms/IPO/FunctionAttrs.cpp =================================================================== --- lib/Transforms/IPO/FunctionAttrs.cpp +++ lib/Transforms/IPO/FunctionAttrs.cpp @@ -930,6 +930,59 @@ return MadeChange; } +/// Remove 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) { + // Check whether all of the functions in the SCC can be made non-convergent + // (because they call only non-convergent functions). + for (CallGraphNode *CGN : SCC) { + Function *F = CGN->getFunction(); + // If any of the SCC's nodes are undefined (e.g. indirect calls) or + // declarations, leave the whole SCC unmodified. + if (!F || F->isDeclaration()) return false; + + if (!F->isConvergent()) continue; + + // If any of the SCC's functions is optnone and convergent, we can't remove + // the convergent attr from any of the SCC's functions. + if (F->hasFnAttribute(Attribute::OptimizeNone)) return false; + + // All of the SCC's functions must be 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, all of the SCC's functions 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(); + }; + if (llvm::any_of(*F, [=](BasicBlock &BB) { + return llvm::any_of(BB, IsConvergentIntrinsicCall); + })) + 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()) { + DEBUG(dbgs() << "Removing convergent attr from " << F->getName() << "\n"); + F->setNotConvergent(); + } + return true; +} + static bool setDoesNotRecurse(Function &F) { if (F.doesNotRecurse()) return false; @@ -1006,6 +1059,7 @@ if (!ExternalNode) { Changed |= addNoAliasAttrs(SCCNodes); Changed |= addNonNullAttrs(SCCNodes, *TLI); + Changed |= removeConvergentAttrs(SCC, SCCNodes); } Changed |= addNoRecurseAttrs(SCC); Index: test/Transforms/FunctionAttrs/convergent.ll =================================================================== --- /dev/null +++ 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 +}