Skip to content

[GVNSink] Fix non-determinisms by using a deterministic ordering #90995

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 65 additions & 13 deletions llvm/lib/Transforms/Scalar/GVNSink.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,22 @@ class ModelledPHI {
public:
ModelledPHI() = default;

ModelledPHI(const PHINode *PN) {
// BasicBlock comes first so we sort by basic block pointer order, then by value pointer order.
SmallVector<std::pair<BasicBlock *, Value *>, 4> Ops;
ModelledPHI(const PHINode *PN,
const DenseMap<const BasicBlock *, unsigned> &BlockOrder) {
// BasicBlock comes first so we sort by basic block pointer order,
// then by value pointer order. No need to call `verifyModelledPHI`
// As the Values and Blocks are populated in a deterministic order.
using OpsType = std::pair<BasicBlock *, Value *>;
SmallVector<OpsType, 4> Ops;
for (unsigned I = 0, E = PN->getNumIncomingValues(); I != E; ++I)
Ops.push_back({PN->getIncomingBlock(I), PN->getIncomingValue(I)});
llvm::sort(Ops);

auto ComesBefore = [BlockOrder](OpsType O1, OpsType O2) {
return BlockOrder.lookup(O1.first) < BlockOrder.lookup(O2.first);
};
// Sort in a deterministic order.
llvm::sort(Ops, ComesBefore);

for (auto &P : Ops) {
Blocks.push_back(P.first);
Values.push_back(P.second);
Expand All @@ -247,16 +257,38 @@ class ModelledPHI {
return M;
}

void
verifyModelledPHI(const DenseMap<const BasicBlock *, unsigned> &BlockOrder) {
assert(Values.size() > 1 && Blocks.size() > 1 &&
"Modelling PHI with less than 2 values");
auto ComesBefore = [BlockOrder](const BasicBlock *BB1,
const BasicBlock *BB2) {
return BlockOrder.lookup(BB1) < BlockOrder.lookup(BB2);
};
assert(llvm::is_sorted(Blocks, ComesBefore));
int C = 0;
llvm::for_each(Values, [&C, this](const Value *V) {
if (!isa<UndefValue>(V)) {
const Instruction *I = cast<Instruction>(V);
assert(I->getParent() == this->Blocks[C]);
}
C++;
});
}
/// Create a PHI from an array of incoming values and incoming blocks.
template <typename VArray, typename BArray>
ModelledPHI(const VArray &V, const BArray &B) {
ModelledPHI(SmallVectorImpl<Instruction *> &V,
SmallSetVector<BasicBlock *, 4> &B,
const DenseMap<const BasicBlock *, unsigned> &BlockOrder) {
// The order of Values and Blocks are already ordered by the caller.
llvm::copy(V, std::back_inserter(Values));
llvm::copy(B, std::back_inserter(Blocks));
verifyModelledPHI(BlockOrder);
}

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

// Hash functor
unsigned hash() const {
return (unsigned)hash_combine_range(Values.begin(), Values.end());
// Is deterministic because Values are saved in a specific order.
return (unsigned)hash_combine_range(Values.begin(), Values.end());
}

bool operator==(const ModelledPHI &Other) const {
Expand Down Expand Up @@ -566,7 +599,7 @@ class ValueTable {

class GVNSink {
public:
GVNSink() = default;
GVNSink() {}

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

Expand All @@ -583,6 +626,7 @@ class GVNSink {

private:
ValueTable VN;
DenseMap<const BasicBlock *, unsigned> RPOTOrder;

bool shouldAvoidSinkingInstruction(Instruction *I) {
// These instructions may change or break semantics if moved.
Expand All @@ -603,7 +647,7 @@ class GVNSink {
void analyzeInitialPHIs(BasicBlock *BB, ModelledPHISet &PHIs,
SmallPtrSetImpl<Value *> &PHIContents) {
for (PHINode &PN : BB->phis()) {
auto MPHI = ModelledPHI(&PN);
auto MPHI = ModelledPHI(&PN, RPOTOrder);
PHIs.insert(MPHI);
for (auto *V : MPHI.getValues())
PHIContents.insert(V);
Expand Down Expand Up @@ -691,7 +735,7 @@ GVNSink::analyzeInstructionForSinking(LockstepReverseIterator &LRI,
}

// The sunk instruction's results.
ModelledPHI NewPHI(NewInsts, ActivePreds);
ModelledPHI NewPHI(NewInsts, ActivePreds, RPOTOrder);

// Does sinking this instruction render previous PHIs redundant?
if (NeededPHIs.erase(NewPHI))
Expand Down Expand Up @@ -766,6 +810,9 @@ unsigned GVNSink::sinkBB(BasicBlock *BBEnd) {
BBEnd->printAsOperand(dbgs()); dbgs() << "\n");
SmallVector<BasicBlock *, 4> Preds;
for (auto *B : predecessors(BBEnd)) {
// Bailout on basic blocks without predecessor(PR42346).
if (!RPOTOrder.count(B))
return 0;
auto *T = B->getTerminator();
if (isa<BranchInst>(T) || isa<SwitchInst>(T))
Preds.push_back(B);
Expand All @@ -774,7 +821,11 @@ unsigned GVNSink::sinkBB(BasicBlock *BBEnd) {
}
if (Preds.size() < 2)
return 0;
llvm::sort(Preds);
auto ComesBefore = [this](const BasicBlock *BB1, const BasicBlock *BB2) {
return RPOTOrder.lookup(BB1) < RPOTOrder.lookup(BB2);
};
// Sort in a deterministic order.
llvm::sort(Preds, ComesBefore);

unsigned NumOrigPreds = Preds.size();
// We can only sink instructions through unconditional branches.
Expand Down Expand Up @@ -889,5 +940,6 @@ PreservedAnalyses GVNSinkPass::run(Function &F, FunctionAnalysisManager &AM) {
GVNSink G;
if (!G.run(F))
return PreservedAnalyses::all();

return PreservedAnalyses::none();
}
26 changes: 26 additions & 0 deletions llvm/test/Transforms/GVNSink/int_sideeffect.ll
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,29 @@ if.end:
ret float %phi
}

; CHECK-LABEL: scalarsSinkingReverse
; CHECK-NOT: fmul
; CHECK: = phi
; CHECK: = fmul
define float @scalarsSinkingReverse(float %d, float %m, float %a, i1 %cmp) {
; This test is just a reverse(graph mirror) of the test
; above to ensure GVNSink doesn't depend on the order of branches.
entry:
br i1 %cmp, label %if.then, label %if.else

if.then:
%add = fadd float %m, %a
%mul1 = fmul float %add, %d
br label %if.end

if.else:
call void @llvm.sideeffect()
%sub = fsub float %m, %a
%mul0 = fmul float %sub, %d
br label %if.end

if.end:
%phi = phi float [ %mul1, %if.then ], [ %mul0, %if.else ]
ret float %phi
}

Loading