Skip to content

Commit

Permalink
SafepointIRVerifier should ignore dead blocks and dead edges
Browse files Browse the repository at this point in the history
Not only should SafepointIRVerifier ignore unreachable blocks (as suggested in https://reviews.llvm.org/D47011) but it also has to ignore dead blocks.

In @test2 (see the new tests):

  br i1 true, label %right, label %left
left:
  ...
right:
  ...
merge:
  %val = phi i8 addrspace(1)* [ ..., %left ], [ ..., %right ]
  use %val
both left and right branches are reachable.
If they collide then SafepointIRVerifier reports an error.

Because of the foldable branch condition GVN finds the left branch dead and removes the phi node entry that merges values from right and left. Then the use comes from the right branch. This results in no collision.

So, SafepointIRVerifier ends up in different results depending on either GVN is run or not.

To solve this issue this patch adds Dead Block detection to SafepointIRVerifier which can ignore dead blocks while validating IR. The Dead Block detection algorithm is taken from GVN but modified to not split critical edges. That is needed to keep CFG unchanged by SafepointIRVerifier.

Patch by Yevgeny Rouban.

Reviewed By: anna, apilipenko, DaniilSuchkov

Differential Revision: https://reviews.llvm.org/D47441

llvm-svn: 335473
  • Loading branch information
arpilipe committed Jun 25, 2018
1 parent 4581f37 commit ac6e686
Showing 1 changed file with 189 additions and 28 deletions.
217 changes: 189 additions & 28 deletions llvm/lib/IR/SafepointIRVerifier.cpp
Expand Up @@ -59,23 +59,162 @@ using namespace llvm;
static cl::opt<bool> PrintOnly("safepoint-ir-verifier-print-only",
cl::init(false));

static void Verify(const Function &F, const DominatorTree &DT);
namespace {

/// This CFG Deadness finds dead blocks and edges. Algorithm starts with a set
/// of blocks unreachable from entry then propagates deadness using foldable
/// conditional branches without modifying CFG. So GVN does but it changes CFG
/// by splitting critical edges. In most cases passes rely on SimplifyCFG to
/// clean up dead blocks, but in some cases, like verification or loop passes
/// it's not possible.
class CFGDeadness {
const DominatorTree *DT = nullptr;
SetVector<const BasicBlock *> DeadBlocks;
SetVector<const Use *> DeadEdges; // Contains all dead edges from live blocks.

public:
/// Return the edge that coresponds to the predecessor.
static const Use& getEdge(const_pred_iterator &PredIt) {
auto &PU = PredIt.getUse();
return PU.getUser()->getOperandUse(PU.getOperandNo());
}

/// Return true if there is at least one live edge that corresponds to the
/// basic block InBB listed in the phi node.
bool hasLiveIncomingEdge(const PHINode *PN, const BasicBlock *InBB) const {
assert(!isDeadBlock(InBB) && "block must be live");
const BasicBlock* BB = PN->getParent();
bool Listed = false;
for (const_pred_iterator PredIt(BB), End(BB, true); PredIt != End; ++PredIt) {
if (InBB == *PredIt) {
if (!isDeadEdge(&getEdge(PredIt)))
return true;
Listed = true;
}
}
assert(Listed && "basic block is not found among incoming blocks");
return false;
}


bool isDeadBlock(const BasicBlock *BB) const {
return DeadBlocks.count(BB);
}

bool isDeadEdge(const Use *U) const {
assert(dyn_cast<Instruction>(U->getUser())->isTerminator() &&
"edge must be operand of terminator");
assert(cast_or_null<BasicBlock>(U->get()) &&
"edge must refer to basic block");
assert(!isDeadBlock(dyn_cast<Instruction>(U->getUser())->getParent()) &&
"isDeadEdge() must be applied to edge from live block");
return DeadEdges.count(U);
}

bool hasLiveIncomingEdges(const BasicBlock *BB) const {
// Check if all incoming edges are dead.
for (const_pred_iterator PredIt(BB), End(BB, true); PredIt != End; ++PredIt) {
auto &PU = PredIt.getUse();
const Use &U = PU.getUser()->getOperandUse(PU.getOperandNo());
if (!isDeadBlock(*PredIt) && !isDeadEdge(&U))
return true; // Found a live edge.
}
return false;
}

void processFunction(const Function &F, const DominatorTree &DT) {
this->DT = &DT;

// Start with all blocks unreachable from entry.
for (const BasicBlock &BB : F)
if (!DT.isReachableFromEntry(&BB))
DeadBlocks.insert(&BB);

// Top-down walk of the dominator tree
ReversePostOrderTraversal<const Function *> RPOT(&F);
for (const BasicBlock *BB : RPOT) {
const TerminatorInst *TI = BB->getTerminator();
assert(TI && "blocks must be well formed");

// For conditional branches, we can perform simple conditional propagation on
// the condition value itself.
const BranchInst *BI = dyn_cast<BranchInst>(TI);
if (!BI || !BI->isConditional() || !isa<Constant>(BI->getCondition()))
continue;

// If a branch has two identical successors, we cannot declare either dead.
if (BI->getSuccessor(0) == BI->getSuccessor(1))
continue;

ConstantInt *Cond = dyn_cast<ConstantInt>(BI->getCondition());
if (!Cond)
continue;

addDeadEdge(BI->getOperandUse(Cond->getZExtValue() ? 1 : 2));
}
}

protected:
void addDeadBlock(const BasicBlock *BB) {
SmallVector<const BasicBlock *, 4> NewDead;
SmallSetVector<const BasicBlock *, 4> DF;

NewDead.push_back(BB);
while (!NewDead.empty()) {
const BasicBlock *D = NewDead.pop_back_val();
if (isDeadBlock(D))
continue;

// All blocks dominated by D are dead.
SmallVector<BasicBlock *, 8> Dom;
DT->getDescendants(const_cast<BasicBlock*>(D), Dom);
// Do not need to mark all in and out edges dead
// because BB is marked dead and this is enough
// to run further.
DeadBlocks.insert(Dom.begin(), Dom.end());

// Figure out the dominance-frontier(D).
for (BasicBlock *B : Dom)
for (BasicBlock *S : successors(B))
if (!isDeadBlock(S) && !hasLiveIncomingEdges(S))
NewDead.push_back(S);
}
}

void addDeadEdge(const Use &DeadEdge) {
if (!DeadEdges.insert(&DeadEdge))
return;

BasicBlock *BB = cast_or_null<BasicBlock>(DeadEdge.get());
if (hasLiveIncomingEdges(BB))
return;

addDeadBlock(BB);
}
};
} // namespace

static void Verify(const Function &F, const DominatorTree &DT,
const CFGDeadness &CD);

namespace {

struct SafepointIRVerifier : public FunctionPass {
static char ID; // Pass identification, replacement for typeid
DominatorTree DT;
SafepointIRVerifier() : FunctionPass(ID) {
initializeSafepointIRVerifierPass(*PassRegistry::getPassRegistry());
}

bool runOnFunction(Function &F) override {
DT.recalculate(F);
Verify(F, DT);
auto &DT = getAnalysis<DominatorTreeWrapperPass>().getDomTree();
CFGDeadness CD;
CD.processFunction(F, DT);
Verify(F, DT, CD);
return false; // no modifications
}

void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.addRequiredID(DominatorTreeWrapperPass::ID);
AU.setPreservesAll();
}

Expand All @@ -95,9 +234,10 @@ FunctionPass *llvm::createSafepointIRVerifierPass() {
}

INITIALIZE_PASS_BEGIN(SafepointIRVerifier, "verify-safepoint-ir",
"Safepoint IR Verifier", false, true)
"Safepoint IR Verifier", false, false)
INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass)
INITIALIZE_PASS_END(SafepointIRVerifier, "verify-safepoint-ir",
"Safepoint IR Verifier", false, true)
"Safepoint IR Verifier", false, false)

static bool isGCPointerType(Type *T) {
if (auto *PT = dyn_cast<PointerType>(T))
Expand Down Expand Up @@ -292,6 +432,7 @@ class InstructionVerifier;
/// considered to be unrelocated and no false alarm will happen.
class GCPtrTracker {
const Function &F;
const CFGDeadness &CD;
SpecificBumpPtrAllocator<BasicBlockState> BSAllocator;
DenseMap<const BasicBlock *, BasicBlockState *> BlockMap;
// This set contains defs of unrelocated pointers that are proved to be legal
Expand All @@ -302,7 +443,12 @@ class GCPtrTracker {
DenseSet<const Value *> PoisonedDefs;

public:
GCPtrTracker(const Function &F, const DominatorTree &DT);
GCPtrTracker(const Function &F, const DominatorTree &DT,
const CFGDeadness &CD);

bool hasLiveIncomingEdge(const PHINode *PN, const BasicBlock *InBB) const {
return CD.hasLiveIncomingEdge(PN, InBB);
}

BasicBlockState *getBasicBlockState(const BasicBlock *BB);
const BasicBlockState *getBasicBlockState(const BasicBlock *BB) const;
Expand All @@ -318,8 +464,7 @@ class GCPtrTracker {
static void verifyFunction(GCPtrTracker &&Tracker,
InstructionVerifier &Verifier);

/// Returns true for reachable blocks that are verified, the other blocks are
/// ignored.
/// Returns true for reachable and live blocks.
bool isMapped(const BasicBlock *BB) const {
return BlockMap.find(BB) != BlockMap.end();
}
Expand Down Expand Up @@ -378,10 +523,12 @@ class InstructionVerifier {
};
} // end anonymous namespace

GCPtrTracker::GCPtrTracker(const Function &F, const DominatorTree &DT) : F(F) {
// First, calculate Contribution of each BB.
GCPtrTracker::GCPtrTracker(const Function &F, const DominatorTree &DT,
const CFGDeadness &CD) : F(F), CD(CD) {
// Calculate Contribution of each live BB.
// Allocate BB states for live blocks.
for (const BasicBlock &BB : F)
if (DT.isReachableFromEntry(&BB)) {
if (!CD.isDeadBlock(&BB)) {
BasicBlockState *BBS = new (BSAllocator.Allocate()) BasicBlockState;
for (const auto &I : BB)
transferInstruction(I, BBS->Cleared, BBS->Contribution);
Expand All @@ -403,9 +550,7 @@ GCPtrTracker::GCPtrTracker(const Function &F, const DominatorTree &DT) : F(F) {

BasicBlockState *GCPtrTracker::getBasicBlockState(const BasicBlock *BB) {
auto it = BlockMap.find(BB);
assert(it != BlockMap.end() &&
"No such BB in BlockMap! Probably BB from another function");
return it->second;
return it != BlockMap.end() ? it->second : nullptr;
}

const BasicBlockState *GCPtrTracker::getBasicBlockState(
Expand All @@ -426,6 +571,9 @@ void GCPtrTracker::verifyFunction(GCPtrTracker &&Tracker,
ReversePostOrderTraversal<const Function *> RPOT(&Tracker.F);
for (const BasicBlock *BB : RPOT) {
BasicBlockState *BBS = Tracker.getBasicBlockState(BB);
if (!BBS)
continue;

// We destructively modify AvailableIn as we traverse the block instruction
// by instruction.
AvailableValueSet &AvailableSet = BBS->AvailableIn;
Expand Down Expand Up @@ -455,12 +603,17 @@ void GCPtrTracker::recalculateBBsStates() {
// The AvailableIn and AvailableOut sets decrease as we iterate.
while (!Worklist.empty()) {
const BasicBlock *BB = Worklist.pop_back_val();
BasicBlockState *BBS = BlockMap[BB];
BasicBlockState *BBS = getBasicBlockState(BB);
if (!BBS)
continue; // Ignore dead successors.

size_t OldInCount = BBS->AvailableIn.size();
for (const BasicBlock *PBB : predecessors(BB))
if (isMapped(PBB))
set_intersect(BBS->AvailableIn, BlockMap[PBB]->AvailableOut);
for (const_pred_iterator PredIt(BB), End(BB, true); PredIt != End; ++PredIt) {
const BasicBlock *PBB = *PredIt;
BasicBlockState *PBBS = getBasicBlockState(PBB);
if (PBBS && !CD.isDeadEdge(&CFGDeadness::getEdge(PredIt)))
set_intersect(BBS->AvailableIn, PBBS->AvailableOut);
}

assert(OldInCount >= BBS->AvailableIn.size() && "invariant!");

Expand Down Expand Up @@ -499,8 +652,10 @@ bool GCPtrTracker::removeValidUnrelocatedDefs(const BasicBlock *BB,
bool HasUnrelocatedInputs = false;
for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i) {
const BasicBlock *InBB = PN->getIncomingBlock(i);
if (!isMapped(InBB))
continue;
if (!isMapped(InBB) ||
!CD.hasLiveIncomingEdge(PN, InBB))
continue; // Skip dead block or dead edge.

const Value *InValue = PN->getIncomingValue(i);

if (isNotExclusivelyConstantDerived(InValue)) {
Expand Down Expand Up @@ -573,13 +728,15 @@ void GCPtrTracker::gatherDominatingDefs(const BasicBlock *BB,
assert(DTN && "Unreachable blocks are ignored");
while (DTN->getIDom()) {
DTN = DTN->getIDom();
const auto &Defs = BlockMap[DTN->getBlock()]->Contribution;
auto BBS = getBasicBlockState(DTN->getBlock());
assert(BBS && "immediate dominator cannot be dead for a live block");
const auto &Defs = BBS->Contribution;
Result.insert(Defs.begin(), Defs.end());
// If this block is 'Cleared', then nothing LiveIn to this block can be
// available after this block completes. Note: This turns out to be
// really important for reducing memory consuption of the initial available
// sets and thus peak memory usage by this verifier.
if (BlockMap[DTN->getBlock()]->Cleared)
if (BBS->Cleared)
return;
}

Expand Down Expand Up @@ -628,12 +785,15 @@ void InstructionVerifier::verifyInstruction(
if (containsGCPtrType(PN->getType()))
for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i) {
const BasicBlock *InBB = PN->getIncomingBlock(i);
if (!Tracker->isMapped(InBB))
continue;
const BasicBlockState *InBBS = Tracker->getBasicBlockState(InBB);
if (!InBBS ||
!Tracker->hasLiveIncomingEdge(PN, InBB))
continue; // Skip dead block or dead edge.

const Value *InValue = PN->getIncomingValue(i);

if (isNotExclusivelyConstantDerived(InValue) &&
!Tracker->getBasicBlockState(InBB)->AvailableOut.count(InValue))
!InBBS->AvailableOut.count(InValue))
reportInvalidUse(*InValue, *PN);
}
} else if (isa<CmpInst>(I) &&
Expand Down Expand Up @@ -710,13 +870,14 @@ void InstructionVerifier::reportInvalidUse(const Value &V,
AnyInvalidUses = true;
}

static void Verify(const Function &F, const DominatorTree &DT) {
static void Verify(const Function &F, const DominatorTree &DT,
const CFGDeadness &CD) {
LLVM_DEBUG(dbgs() << "Verifying gc pointers in function: " << F.getName()
<< "\n");
if (PrintOnly)
dbgs() << "Verifying gc pointers in function: " << F.getName() << "\n";

GCPtrTracker Tracker(F, DT);
GCPtrTracker Tracker(F, DT, CD);

// We now have all the information we need to decide if the use of a heap
// reference is legal or not, given our safepoint semantics.
Expand Down

0 comments on commit ac6e686

Please sign in to comment.