diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp --- a/llvm/lib/Analysis/ValueTracking.cpp +++ b/llvm/lib/Analysis/ValueTracking.cpp @@ -4465,10 +4465,16 @@ } case Instruction::Call: { auto *CI = cast(Inst); - const Function *Callee = CI->getCalledFunction(); + + // The called function depends on the set of threads executing it, which + // could change if the call is moved to a different location in control + // flow. + if (CI->isConvergent()) + return false; // The called function could have undefined behavior or side-effects, even // if marked readnone nounwind. + const Function *Callee = CI->getCalledFunction(); return Callee && Callee->isSpeculatable(); } case Instruction::VAArg: diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp --- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp +++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp @@ -1296,6 +1296,13 @@ isa(I1)) return false; + // Cannot hoist convergent calls since that could change the set of threads + // with which they communicate. + if (const auto *C = dyn_cast(I1)) { + if (C->isConvergent()) + return false; + } + BasicBlock *BIParent = BI->getParent(); bool Changed = false; @@ -1518,14 +1525,20 @@ I->getType()->isTokenTy()) return false; - // Conservatively return false if I is an inline-asm instruction. Sinking - // and merging inline-asm instructions can potentially create arguments - // that cannot satisfy the inline-asm constraints. - // If the instruction has nomerge attribute, return false. - if (const auto *C = dyn_cast(I)) + if (const auto *C = dyn_cast(I)) { + // Conservatively return false if I is an inline-asm instruction. Sinking + // and merging inline-asm instructions can potentially create arguments + // that cannot satisfy the inline-asm constraints. + // If the instruction has nomerge attribute, return false. if (C->isInlineAsm() || C->cannotMerge()) return false; + // Do not sink convergent calls, as sinking may affect the set of threads + // with which the convergent operation communicates. + if (C->isConvergent()) + return false; + } + // Each instruction must have zero or one use. if (HasUse && !I->hasOneUse()) return false; diff --git a/llvm/test/Transforms/SimplifyCFG/attr-convergent.ll b/llvm/test/Transforms/SimplifyCFG/attr-convergent.ll --- a/llvm/test/Transforms/SimplifyCFG/attr-convergent.ll +++ b/llvm/test/Transforms/SimplifyCFG/attr-convergent.ll @@ -3,6 +3,7 @@ ; Checks that the SimplifyCFG pass won't duplicate a call to a function marked ; convergent. ; +; CHECK-LABEL: @check ; CHECK: call void @barrier ; CHECK-NOT: call void @barrier define void @check(i1 %cond, i32* %out) { @@ -25,4 +26,51 @@ ret void } +; CHECK-LABEL: @dont_merge_convergent +; CHECK: if.then: +; CHECK: %ballot.then = call i32 @ballot +; CHECK: if.else: +; CHECK: %ballot.else = call i32 @ballot +define i32 @dont_merge_convergent(i1 %cond1, i1 %cond2) { +entry: + %tok = call token @llvm.experimental.convergence.anchor() + br i1 %cond1, label %if.then, label %if.else + +if.then: + %ballot.then = call i32 @ballot(i1 %cond2) [ "convergencectrl"(token %tok) ] + br label %end + +if.else: + %ballot.else = call i32 @ballot(i1 %cond2) [ "convergencectrl"(token %tok) ] + br label %end + +end: + %ballot = phi i32 [ %ballot.then, %if.then ], [ %ballot.else, %if.else ] + ret i32 %ballot +} + +; CHECK-LABEL: @dont_speculate_convergent +; CHECK: entry: +; CHECK: then: +; CHECK: call i32 @speculatable_convergent +; CHECK: end: +define i32 @dont_speculate_convergent(i1 %cond1, i32 %in) { +entry: + %tok = call token @llvm.experimental.convergence.anchor() + br i1 %cond1, label %then, label %end + +then: + %v = call i32 @speculatable_convergent(i32 %in) [ "convergencectrl"(token %tok) ] + br label %end + +end: + %r = phi i32 [ 0, %entry ], [ %v, %then ] + ret i32 %r +} + +declare token @llvm.experimental.convergence.anchor() + +declare i32 @ballot(i1 %arg) convergent readnone +declare i32 @speculatable_convergent(i32) convergent readnone speculatable + declare void @barrier() convergent