Skip to content

Commit f16bff1

Browse files
authored
[GVN][NewGVN][Local] Handle attributes for function calls after CSE (#114011)
This patch intersects attributes of two calls to avoid introducing UB. It also skips incompatible call pairs in GVN/NewGVN. However, I cannot provide negative tests for these changes. Fixes #113997.
1 parent bef3b54 commit f16bff1

File tree

5 files changed

+114
-7
lines changed

5 files changed

+114
-7
lines changed

llvm/lib/Transforms/Scalar/GVN.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2189,6 +2189,16 @@ bool GVNPass::processAssumeIntrinsic(AssumeInst *IntrinsicI) {
21892189
return Changed;
21902190
}
21912191

2192+
// Return true iff V1 can be replaced with V2.
2193+
static bool canBeReplacedBy(Value *V1, Value *V2) {
2194+
if (auto *CB1 = dyn_cast<CallBase>(V1))
2195+
if (auto *CB2 = dyn_cast<CallBase>(V2))
2196+
return CB1->getAttributes()
2197+
.intersectWith(CB2->getContext(), CB2->getAttributes())
2198+
.has_value();
2199+
return true;
2200+
}
2201+
21922202
static void patchAndReplaceAllUsesWith(Instruction *I, Value *Repl) {
21932203
patchReplacementInstruction(I, Repl);
21942204
I->replaceAllUsesWith(Repl);
@@ -2734,7 +2744,7 @@ bool GVNPass::processInstruction(Instruction *I) {
27342744
// Perform fast-path value-number based elimination of values inherited from
27352745
// dominators.
27362746
Value *Repl = findLeader(I->getParent(), Num);
2737-
if (!Repl) {
2747+
if (!Repl || !canBeReplacedBy(I, Repl)) {
27382748
// Failure, just remember this instance for future use.
27392749
LeaderTable.insert(Num, I, I->getParent());
27402750
return false;
@@ -3000,7 +3010,7 @@ bool GVNPass::performScalarPRE(Instruction *CurInst) {
30003010

30013011
uint32_t TValNo = VN.phiTranslate(P, CurrentBlock, ValNo, *this);
30023012
Value *predV = findLeader(P, TValNo);
3003-
if (!predV) {
3013+
if (!predV || !canBeReplacedBy(CurInst, predV)) {
30043014
predMap.push_back(std::make_pair(static_cast<Value *>(nullptr), P));
30053015
PREPred = P;
30063016
++NumWithout;

llvm/lib/Transforms/Scalar/NewGVN.cpp

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3854,6 +3854,16 @@ Value *NewGVN::findPHIOfOpsLeader(const Expression *E,
38543854
return nullptr;
38553855
}
38563856

3857+
// Return true iff V1 can be replaced with V2.
3858+
static bool canBeReplacedBy(Value *V1, Value *V2) {
3859+
if (auto *CB1 = dyn_cast<CallBase>(V1))
3860+
if (auto *CB2 = dyn_cast<CallBase>(V2))
3861+
return CB1->getAttributes()
3862+
.intersectWith(CB2->getContext(), CB2->getAttributes())
3863+
.has_value();
3864+
return true;
3865+
}
3866+
38573867
bool NewGVN::eliminateInstructions(Function &F) {
38583868
// This is a non-standard eliminator. The normal way to eliminate is
38593869
// to walk the dominator tree in order, keeping track of available
@@ -3963,6 +3973,9 @@ bool NewGVN::eliminateInstructions(Function &F) {
39633973
MembersLeft.insert(Member);
39643974
continue;
39653975
}
3976+
if (!canBeReplacedBy(Member, Leader))
3977+
continue;
3978+
39663979
LLVM_DEBUG(dbgs() << "Found replacement " << *(Leader) << " for "
39673980
<< *Member << "\n");
39683981
auto *I = cast<Instruction>(Member);
@@ -4069,8 +4082,11 @@ bool NewGVN::eliminateInstructions(Function &F) {
40694082
if (DominatingLeader != Def) {
40704083
// Even if the instruction is removed, we still need to update
40714084
// flags/metadata due to downstreams users of the leader.
4072-
if (!match(DefI, m_Intrinsic<Intrinsic::ssa_copy>()))
4085+
if (!match(DefI, m_Intrinsic<Intrinsic::ssa_copy>())) {
4086+
if (!canBeReplacedBy(DefI, DominatingLeader))
4087+
continue;
40734088
patchReplacementInstruction(DefI, DominatingLeader);
4089+
}
40744090

40754091
markInstructionForDeletion(DefI);
40764092
}
@@ -4112,17 +4128,21 @@ bool NewGVN::eliminateInstructions(Function &F) {
41124128
// Don't replace our existing users with ourselves.
41134129
if (U->get() == DominatingLeader)
41144130
continue;
4115-
LLVM_DEBUG(dbgs()
4116-
<< "Found replacement " << *DominatingLeader << " for "
4117-
<< *U->get() << " in " << *(U->getUser()) << "\n");
41184131

41194132
// If we replaced something in an instruction, handle the patching of
41204133
// metadata. Skip this if we are replacing predicateinfo with its
41214134
// original operand, as we already know we can just drop it.
41224135
auto *ReplacedInst = cast<Instruction>(U->get());
41234136
auto *PI = PredInfo->getPredicateInfoFor(ReplacedInst);
4124-
if (!PI || DominatingLeader != PI->OriginalOp)
4137+
if (!PI || DominatingLeader != PI->OriginalOp) {
4138+
if (!canBeReplacedBy(ReplacedInst, DominatingLeader))
4139+
continue;
41254140
patchReplacementInstruction(ReplacedInst, DominatingLeader);
4141+
}
4142+
4143+
LLVM_DEBUG(dbgs()
4144+
<< "Found replacement " << *DominatingLeader << " for "
4145+
<< *U->get() << " in " << *(U->getUser()) << "\n");
41264146
U->set(DominatingLeader);
41274147
// This is now a use of the dominating leader, which means if the
41284148
// dominating leader was dead, it's now live!

llvm/lib/Transforms/Utils/Local.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3508,6 +3508,17 @@ void llvm::patchReplacementInstruction(Instruction *I, Value *Repl) {
35083508
else if (!isa<LoadInst>(I))
35093509
ReplInst->andIRFlags(I);
35103510

3511+
// Handle attributes.
3512+
if (auto *CB1 = dyn_cast<CallBase>(ReplInst)) {
3513+
if (auto *CB2 = dyn_cast<CallBase>(I)) {
3514+
bool Success = CB1->tryIntersectAttributes(CB2);
3515+
assert(Success && "We should not be trying to sink callbases "
3516+
"with non-intersectable attributes");
3517+
// For NDEBUG Compile.
3518+
(void)Success;
3519+
}
3520+
}
3521+
35113522
// FIXME: If both the original and replacement value are part of the
35123523
// same control-flow region (meaning that the execution of one
35133524
// guarantees the execution of the other), then we can combine the

llvm/test/Transforms/GVN/pr113997.ll

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
2+
; RUN: opt -S -passes=gvn < %s | FileCheck %s
3+
4+
; Make sure attributes in function calls are intersected correctly.
5+
6+
define i1 @bucket(i32 noundef %x) {
7+
; CHECK-LABEL: define i1 @bucket(
8+
; CHECK-SAME: i32 noundef [[X:%.*]]) {
9+
; CHECK-NEXT: [[CMP1:%.*]] = icmp sgt i32 [[X]], 0
10+
; CHECK-NEXT: [[CTPOP1:%.*]] = tail call range(i32 0, 33) i32 @llvm.ctpop.i32(i32 [[X]])
11+
; CHECK-NEXT: [[CMP2:%.*]] = icmp samesign ult i32 [[CTPOP1]], 2
12+
; CHECK-NEXT: [[COND:%.*]] = select i1 [[CMP1]], i1 [[CMP2]], i1 false
13+
; CHECK-NEXT: br i1 [[COND]], label %[[IF_THEN:.*]], label %[[IF_ELSE:.*]]
14+
; CHECK: [[IF_ELSE]]:
15+
; CHECK-NEXT: [[RES:%.*]] = icmp eq i32 [[CTPOP1]], 1
16+
; CHECK-NEXT: ret i1 [[RES]]
17+
; CHECK: [[IF_THEN]]:
18+
; CHECK-NEXT: ret i1 false
19+
;
20+
%cmp1 = icmp sgt i32 %x, 0
21+
%ctpop1 = tail call range(i32 1, 32) i32 @llvm.ctpop.i32(i32 %x)
22+
%cmp2 = icmp samesign ult i32 %ctpop1, 2
23+
%cond = select i1 %cmp1, i1 %cmp2, i1 false
24+
br i1 %cond, label %if.then, label %if.else
25+
26+
if.else:
27+
%ctpop2 = tail call range(i32 0, 33) i32 @llvm.ctpop.i32(i32 %x)
28+
%res = icmp eq i32 %ctpop2, 1
29+
ret i1 %res
30+
31+
if.then:
32+
ret i1 false
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
2+
; RUN: opt -S -passes=newgvn < %s | FileCheck %s
3+
4+
; Make sure attributes in function calls are intersected correctly.
5+
6+
define i1 @bucket(i32 noundef %x) {
7+
; CHECK-LABEL: define i1 @bucket(
8+
; CHECK-SAME: i32 noundef [[X:%.*]]) {
9+
; CHECK-NEXT: [[CMP1:%.*]] = icmp sgt i32 [[X]], 0
10+
; CHECK-NEXT: [[CTPOP1:%.*]] = tail call range(i32 0, 33) i32 @llvm.ctpop.i32(i32 [[X]])
11+
; CHECK-NEXT: [[CMP2:%.*]] = icmp samesign ult i32 [[CTPOP1]], 2
12+
; CHECK-NEXT: [[COND:%.*]] = select i1 [[CMP1]], i1 [[CMP2]], i1 false
13+
; CHECK-NEXT: br i1 [[COND]], label %[[IF_THEN:.*]], label %[[IF_ELSE:.*]]
14+
; CHECK: [[IF_ELSE]]:
15+
; CHECK-NEXT: [[RES:%.*]] = icmp eq i32 [[CTPOP1]], 1
16+
; CHECK-NEXT: ret i1 [[RES]]
17+
; CHECK: [[IF_THEN]]:
18+
; CHECK-NEXT: ret i1 false
19+
;
20+
%cmp1 = icmp sgt i32 %x, 0
21+
%ctpop1 = tail call range(i32 1, 32) i32 @llvm.ctpop.i32(i32 %x)
22+
%cmp2 = icmp samesign ult i32 %ctpop1, 2
23+
%cond = select i1 %cmp1, i1 %cmp2, i1 false
24+
br i1 %cond, label %if.then, label %if.else
25+
26+
if.else:
27+
%ctpop2 = tail call range(i32 0, 33) i32 @llvm.ctpop.i32(i32 %x)
28+
%res = icmp eq i32 %ctpop2, 1
29+
ret i1 %res
30+
31+
if.then:
32+
ret i1 false
33+
}

0 commit comments

Comments
 (0)