Skip to content

Commit abe3c5a

Browse files
authored
[GVNSink] Fix non-determinisms by using a deterministic ordering (#90995)
GVNSink used to order instructions based on their pointer values and was prone to non-determinism because of that. This patch ensures all the values stored are using a deterministic order. I have also added a verfier(`ModelledPHI::verifyModelledPHI`) to assert when ordering isn't preserved. Additionally, I have added a test case (mirror graph image of an existing test) that would have failed before this patch. Fixes: #77852
1 parent de641e2 commit abe3c5a

File tree

2 files changed

+91
-13
lines changed

2 files changed

+91
-13
lines changed

llvm/lib/Transforms/Scalar/GVNSink.cpp

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -226,12 +226,22 @@ class ModelledPHI {
226226
public:
227227
ModelledPHI() = default;
228228

229-
ModelledPHI(const PHINode *PN) {
230-
// BasicBlock comes first so we sort by basic block pointer order, then by value pointer order.
231-
SmallVector<std::pair<BasicBlock *, Value *>, 4> Ops;
229+
ModelledPHI(const PHINode *PN,
230+
const DenseMap<const BasicBlock *, unsigned> &BlockOrder) {
231+
// BasicBlock comes first so we sort by basic block pointer order,
232+
// then by value pointer order. No need to call `verifyModelledPHI`
233+
// As the Values and Blocks are populated in a deterministic order.
234+
using OpsType = std::pair<BasicBlock *, Value *>;
235+
SmallVector<OpsType, 4> Ops;
232236
for (unsigned I = 0, E = PN->getNumIncomingValues(); I != E; ++I)
233237
Ops.push_back({PN->getIncomingBlock(I), PN->getIncomingValue(I)});
234-
llvm::sort(Ops);
238+
239+
auto ComesBefore = [BlockOrder](OpsType O1, OpsType O2) {
240+
return BlockOrder.lookup(O1.first) < BlockOrder.lookup(O2.first);
241+
};
242+
// Sort in a deterministic order.
243+
llvm::sort(Ops, ComesBefore);
244+
235245
for (auto &P : Ops) {
236246
Blocks.push_back(P.first);
237247
Values.push_back(P.second);
@@ -247,16 +257,38 @@ class ModelledPHI {
247257
return M;
248258
}
249259

260+
void
261+
verifyModelledPHI(const DenseMap<const BasicBlock *, unsigned> &BlockOrder) {
262+
assert(Values.size() > 1 && Blocks.size() > 1 &&
263+
"Modelling PHI with less than 2 values");
264+
auto ComesBefore = [BlockOrder](const BasicBlock *BB1,
265+
const BasicBlock *BB2) {
266+
return BlockOrder.lookup(BB1) < BlockOrder.lookup(BB2);
267+
};
268+
assert(llvm::is_sorted(Blocks, ComesBefore));
269+
int C = 0;
270+
llvm::for_each(Values, [&C, this](const Value *V) {
271+
if (!isa<UndefValue>(V)) {
272+
const Instruction *I = cast<Instruction>(V);
273+
assert(I->getParent() == this->Blocks[C]);
274+
}
275+
C++;
276+
});
277+
}
250278
/// Create a PHI from an array of incoming values and incoming blocks.
251-
template <typename VArray, typename BArray>
252-
ModelledPHI(const VArray &V, const BArray &B) {
279+
ModelledPHI(SmallVectorImpl<Instruction *> &V,
280+
SmallSetVector<BasicBlock *, 4> &B,
281+
const DenseMap<const BasicBlock *, unsigned> &BlockOrder) {
282+
// The order of Values and Blocks are already ordered by the caller.
253283
llvm::copy(V, std::back_inserter(Values));
254284
llvm::copy(B, std::back_inserter(Blocks));
285+
verifyModelledPHI(BlockOrder);
255286
}
256287

257288
/// Create a PHI from [I[OpNum] for I in Insts].
258-
template <typename BArray>
259-
ModelledPHI(ArrayRef<Instruction *> Insts, unsigned OpNum, const BArray &B) {
289+
/// TODO: Figure out a way to verifyModelledPHI in this constructor.
290+
ModelledPHI(ArrayRef<Instruction *> Insts, unsigned OpNum,
291+
SmallSetVector<BasicBlock *, 4> &B) {
260292
llvm::copy(B, std::back_inserter(Blocks));
261293
for (auto *I : Insts)
262294
Values.push_back(I->getOperand(OpNum));
@@ -297,7 +329,8 @@ class ModelledPHI {
297329

298330
// Hash functor
299331
unsigned hash() const {
300-
return (unsigned)hash_combine_range(Values.begin(), Values.end());
332+
// Is deterministic because Values are saved in a specific order.
333+
return (unsigned)hash_combine_range(Values.begin(), Values.end());
301334
}
302335

303336
bool operator==(const ModelledPHI &Other) const {
@@ -566,7 +599,7 @@ class ValueTable {
566599

567600
class GVNSink {
568601
public:
569-
GVNSink() = default;
602+
GVNSink() {}
570603

571604
bool run(Function &F) {
572605
LLVM_DEBUG(dbgs() << "GVNSink: running on function @" << F.getName()
@@ -575,6 +608,16 @@ class GVNSink {
575608
unsigned NumSunk = 0;
576609
ReversePostOrderTraversal<Function*> RPOT(&F);
577610
VN.setReachableBBs(BasicBlocksSet(RPOT.begin(), RPOT.end()));
611+
// Populate reverse post-order to order basic blocks in deterministic
612+
// order. Any arbitrary ordering will work in this case as long as they are
613+
// deterministic. The node ordering of newly created basic blocks
614+
// are irrelevant because RPOT(for computing sinkable candidates) is also
615+
// obtained ahead of time and only their order are relevant for this pass.
616+
unsigned NodeOrdering = 0;
617+
RPOTOrder[*RPOT.begin()] = ++NodeOrdering;
618+
for (auto *BB : RPOT)
619+
if (!pred_empty(BB))
620+
RPOTOrder[BB] = ++NodeOrdering;
578621
for (auto *N : RPOT)
579622
NumSunk += sinkBB(N);
580623

@@ -583,6 +626,7 @@ class GVNSink {
583626

584627
private:
585628
ValueTable VN;
629+
DenseMap<const BasicBlock *, unsigned> RPOTOrder;
586630

587631
bool shouldAvoidSinkingInstruction(Instruction *I) {
588632
// These instructions may change or break semantics if moved.
@@ -603,7 +647,7 @@ class GVNSink {
603647
void analyzeInitialPHIs(BasicBlock *BB, ModelledPHISet &PHIs,
604648
SmallPtrSetImpl<Value *> &PHIContents) {
605649
for (PHINode &PN : BB->phis()) {
606-
auto MPHI = ModelledPHI(&PN);
650+
auto MPHI = ModelledPHI(&PN, RPOTOrder);
607651
PHIs.insert(MPHI);
608652
for (auto *V : MPHI.getValues())
609653
PHIContents.insert(V);
@@ -691,7 +735,7 @@ GVNSink::analyzeInstructionForSinking(LockstepReverseIterator &LRI,
691735
}
692736

693737
// The sunk instruction's results.
694-
ModelledPHI NewPHI(NewInsts, ActivePreds);
738+
ModelledPHI NewPHI(NewInsts, ActivePreds, RPOTOrder);
695739

696740
// Does sinking this instruction render previous PHIs redundant?
697741
if (NeededPHIs.erase(NewPHI))
@@ -766,6 +810,9 @@ unsigned GVNSink::sinkBB(BasicBlock *BBEnd) {
766810
BBEnd->printAsOperand(dbgs()); dbgs() << "\n");
767811
SmallVector<BasicBlock *, 4> Preds;
768812
for (auto *B : predecessors(BBEnd)) {
813+
// Bailout on basic blocks without predecessor(PR42346).
814+
if (!RPOTOrder.count(B))
815+
return 0;
769816
auto *T = B->getTerminator();
770817
if (isa<BranchInst>(T) || isa<SwitchInst>(T))
771818
Preds.push_back(B);
@@ -774,7 +821,11 @@ unsigned GVNSink::sinkBB(BasicBlock *BBEnd) {
774821
}
775822
if (Preds.size() < 2)
776823
return 0;
777-
llvm::sort(Preds);
824+
auto ComesBefore = [this](const BasicBlock *BB1, const BasicBlock *BB2) {
825+
return RPOTOrder.lookup(BB1) < RPOTOrder.lookup(BB2);
826+
};
827+
// Sort in a deterministic order.
828+
llvm::sort(Preds, ComesBefore);
778829

779830
unsigned NumOrigPreds = Preds.size();
780831
// We can only sink instructions through unconditional branches.
@@ -889,5 +940,6 @@ PreservedAnalyses GVNSinkPass::run(Function &F, FunctionAnalysisManager &AM) {
889940
GVNSink G;
890941
if (!G.run(F))
891942
return PreservedAnalyses::all();
943+
892944
return PreservedAnalyses::none();
893945
}

llvm/test/Transforms/GVNSink/int_sideeffect.ll

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,29 @@ if.end:
2828
ret float %phi
2929
}
3030

31+
; CHECK-LABEL: scalarsSinkingReverse
32+
; CHECK-NOT: fmul
33+
; CHECK: = phi
34+
; CHECK: = fmul
35+
define float @scalarsSinkingReverse(float %d, float %m, float %a, i1 %cmp) {
36+
; This test is just a reverse(graph mirror) of the test
37+
; above to ensure GVNSink doesn't depend on the order of branches.
38+
entry:
39+
br i1 %cmp, label %if.then, label %if.else
40+
41+
if.then:
42+
%add = fadd float %m, %a
43+
%mul1 = fmul float %add, %d
44+
br label %if.end
45+
46+
if.else:
47+
call void @llvm.sideeffect()
48+
%sub = fsub float %m, %a
49+
%mul0 = fmul float %sub, %d
50+
br label %if.end
51+
52+
if.end:
53+
%phi = phi float [ %mul1, %if.then ], [ %mul0, %if.else ]
54+
ret float %phi
55+
}
56+

0 commit comments

Comments
 (0)