#include "llvm/IR/SafepointIRVerifier.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/SetOperations.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Statepoint.h"
#include "llvm/IR/Value.h"
#include "llvm/InitializePasses.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#define DEBUG_TYPE "safepoint-ir-verifier"
using namespace llvm;
static cl::opt<bool> PrintOnly("safepoint-ir-verifier-print-only",
cl::init(false));
namespace {
class CFGDeadness {
const DominatorTree *DT = nullptr;
SetVector<const BasicBlock *> DeadBlocks;
SetVector<const Use *> DeadEdges;
public:
static const Use& getEdge(const_pred_iterator &PredIt) {
auto &PU = PredIt.getUse();
return PU.getUser()->getOperandUse(PU.getOperandNo());
}
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;
}
}
(void)Listed;
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(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(cast<Instruction>(U->getUser())->getParent()) &&
"isDeadEdge() must be applied to edge from live block");
return DeadEdges.count(U);
}
bool hasLiveIncomingEdges(const BasicBlock *BB) const {
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; }
return false;
}
void processFunction(const Function &F, const DominatorTree &DT) {
this->DT = &DT;
for (const BasicBlock &BB : F)
if (!DT.isReachableFromEntry(&BB))
DeadBlocks.insert(&BB);
ReversePostOrderTraversal<const Function *> RPOT(&F);
for (const BasicBlock *BB : RPOT) {
const Instruction *TI = BB->getTerminator();
assert(TI && "blocks must be well formed");
const BranchInst *BI = dyn_cast<BranchInst>(TI);
if (!BI || !BI->isConditional() || !isa<Constant>(BI->getCondition()))
continue;
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;
SmallVector<BasicBlock *, 8> Dom;
DT->getDescendants(const_cast<BasicBlock*>(D), Dom);
DeadBlocks.insert(Dom.begin(), Dom.end());
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);
}
};
}
static void Verify(const Function &F, const DominatorTree &DT,
const CFGDeadness &CD);
namespace llvm {
PreservedAnalyses SafepointIRVerifierPass::run(Function &F,
FunctionAnalysisManager &AM) {
const auto &DT = AM.getResult<DominatorTreeAnalysis>(F);
CFGDeadness CD;
CD.processFunction(F, DT);
Verify(F, DT, CD);
return PreservedAnalyses::all();
}
}
namespace {
struct SafepointIRVerifier : public FunctionPass {
static char ID; SafepointIRVerifier() : FunctionPass(ID) {
initializeSafepointIRVerifierPass(*PassRegistry::getPassRegistry());
}
bool runOnFunction(Function &F) override {
auto &DT = getAnalysis<DominatorTreeWrapperPass>().getDomTree();
CFGDeadness CD;
CD.processFunction(F, DT);
Verify(F, DT, CD);
return false; }
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.addRequiredID(DominatorTreeWrapperPass::ID);
AU.setPreservesAll();
}
StringRef getPassName() const override { return "safepoint verifier"; }
};
}
void llvm::verifySafepointIR(Function &F) {
SafepointIRVerifier pass;
pass.runOnFunction(F);
}
char SafepointIRVerifier::ID = 0;
FunctionPass *llvm::createSafepointIRVerifierPass() {
return new SafepointIRVerifier();
}
INITIALIZE_PASS_BEGIN(SafepointIRVerifier, "verify-safepoint-ir",
"Safepoint IR Verifier", false, false)
INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass)
INITIALIZE_PASS_END(SafepointIRVerifier, "verify-safepoint-ir",
"Safepoint IR Verifier", false, false)
static bool isGCPointerType(Type *T) {
if (auto *PT = dyn_cast<PointerType>(T))
return (1 == PT->getAddressSpace());
return false;
}
static bool containsGCPtrType(Type *Ty) {
if (isGCPointerType(Ty))
return true;
if (VectorType *VT = dyn_cast<VectorType>(Ty))
return isGCPointerType(VT->getScalarType());
if (ArrayType *AT = dyn_cast<ArrayType>(Ty))
return containsGCPtrType(AT->getElementType());
if (StructType *ST = dyn_cast<StructType>(Ty))
return llvm::any_of(ST->elements(), containsGCPtrType);
return false;
}
template<typename IteratorTy>
static void PrintValueSet(raw_ostream &OS, IteratorTy Begin, IteratorTy End) {
OS << "[ ";
while (Begin != End) {
OS << **Begin << " ";
++Begin;
}
OS << "]";
}
using AvailableValueSet = DenseSet<const Value *>;
struct BasicBlockState {
AvailableValueSet AvailableIn;
AvailableValueSet AvailableOut;
AvailableValueSet Contribution;
bool Cleared = false;
};
enum BaseType {
NonConstant = 1, ExclusivelyNull,
ExclusivelySomeConstant };
static enum BaseType getBaseType(const Value *Val) {
SmallVector<const Value *, 32> Worklist;
DenseSet<const Value *> Visited;
bool isExclusivelyDerivedFromNull = true;
Worklist.push_back(Val);
while(!Worklist.empty()) {
const Value *V = Worklist.pop_back_val();
if (!Visited.insert(V).second)
continue;
if (const auto *CI = dyn_cast<CastInst>(V)) {
Worklist.push_back(CI->stripPointerCasts());
continue;
}
if (const auto *GEP = dyn_cast<GetElementPtrInst>(V)) {
Worklist.push_back(GEP->getPointerOperand());
continue;
}
if (const auto *PN = dyn_cast<PHINode>(V)) {
append_range(Worklist, PN->incoming_values());
continue;
}
if (const auto *SI = dyn_cast<SelectInst>(V)) {
Worklist.push_back(SI->getTrueValue());
Worklist.push_back(SI->getFalseValue());
continue;
}
if (const auto *GCRelocate = dyn_cast<GCRelocateInst>(V)) {
Worklist.push_back(GCRelocate->getDerivedPtr());
continue;
}
if (const auto *FI = dyn_cast<FreezeInst>(V)) {
Worklist.push_back(FI->getOperand(0));
continue;
}
if (isa<Constant>(V)) {
if (V != Constant::getNullValue(V->getType()))
isExclusivelyDerivedFromNull = false;
continue;
}
return BaseType::NonConstant;
}
return isExclusivelyDerivedFromNull ? BaseType::ExclusivelyNull
: BaseType::ExclusivelySomeConstant;
}
static bool isNotExclusivelyConstantDerived(const Value *V) {
return getBaseType(V) == BaseType::NonConstant;
}
namespace {
class InstructionVerifier;
class GCPtrTracker {
const Function &F;
const CFGDeadness &CD;
SpecificBumpPtrAllocator<BasicBlockState> BSAllocator;
DenseMap<const BasicBlock *, BasicBlockState *> BlockMap;
DenseSet<const Instruction *> ValidUnrelocatedDefs;
DenseSet<const Value *> PoisonedDefs;
public:
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;
bool isValuePoisoned(const Value *V) const { return PoisonedDefs.count(V); }
static void verifyFunction(GCPtrTracker &&Tracker,
InstructionVerifier &Verifier);
bool isMapped(const BasicBlock *BB) const {
return BlockMap.find(BB) != BlockMap.end();
}
private:
bool instructionMayBeSkipped(const Instruction *I) const;
void recalculateBBsStates();
bool removeValidUnrelocatedDefs(const BasicBlock *BB,
const BasicBlockState *BBS,
AvailableValueSet &Contribution);
void gatherDominatingDefs(const BasicBlock *BB, AvailableValueSet &Result,
const DominatorTree &DT);
static void transferBlock(const BasicBlock *BB, BasicBlockState &BBS,
bool ContributionChanged);
static void transferInstruction(const Instruction &I, bool &Cleared,
AvailableValueSet &Available);
};
class InstructionVerifier {
bool AnyInvalidUses = false;
public:
void verifyInstruction(const GCPtrTracker *Tracker, const Instruction &I,
const AvailableValueSet &AvailableSet);
bool hasAnyInvalidUses() const { return AnyInvalidUses; }
private:
void reportInvalidUse(const Value &V, const Instruction &I);
};
}
GCPtrTracker::GCPtrTracker(const Function &F, const DominatorTree &DT,
const CFGDeadness &CD) : F(F), CD(CD) {
for (const BasicBlock &BB : F)
if (!CD.isDeadBlock(&BB)) {
BasicBlockState *BBS = new (BSAllocator.Allocate()) BasicBlockState;
for (const auto &I : BB)
transferInstruction(I, BBS->Cleared, BBS->Contribution);
BlockMap[&BB] = BBS;
}
for (auto &BBI : BlockMap) {
gatherDominatingDefs(BBI.first, BBI.second->AvailableIn, DT);
transferBlock(BBI.first, *BBI.second, true);
}
recalculateBBsStates();
}
BasicBlockState *GCPtrTracker::getBasicBlockState(const BasicBlock *BB) {
return BlockMap.lookup(BB);
}
const BasicBlockState *GCPtrTracker::getBasicBlockState(
const BasicBlock *BB) const {
return const_cast<GCPtrTracker *>(this)->getBasicBlockState(BB);
}
bool GCPtrTracker::instructionMayBeSkipped(const Instruction *I) const {
return ValidUnrelocatedDefs.count(I) || PoisonedDefs.count(I);
}
void GCPtrTracker::verifyFunction(GCPtrTracker &&Tracker,
InstructionVerifier &Verifier) {
ReversePostOrderTraversal<const Function *> RPOT(&Tracker.F);
for (const BasicBlock *BB : RPOT) {
BasicBlockState *BBS = Tracker.getBasicBlockState(BB);
if (!BBS)
continue;
AvailableValueSet &AvailableSet = BBS->AvailableIn;
for (const Instruction &I : *BB) {
if (Tracker.instructionMayBeSkipped(&I))
continue;
Verifier.verifyInstruction(&Tracker, I, AvailableSet);
bool Cleared = false;
transferInstruction(I, Cleared, AvailableSet);
(void)Cleared;
}
}
}
void GCPtrTracker::recalculateBBsStates() {
SetVector<const BasicBlock *> Worklist;
for (auto &BBI : BlockMap)
Worklist.insert(BBI.first);
while (!Worklist.empty()) {
const BasicBlock *BB = Worklist.pop_back_val();
BasicBlockState *BBS = getBasicBlockState(BB);
if (!BBS)
continue;
size_t OldInCount = BBS->AvailableIn.size();
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!");
bool InputsChanged = OldInCount != BBS->AvailableIn.size();
bool ContributionChanged =
removeValidUnrelocatedDefs(BB, BBS, BBS->Contribution);
if (!InputsChanged && !ContributionChanged)
continue;
size_t OldOutCount = BBS->AvailableOut.size();
transferBlock(BB, *BBS, ContributionChanged);
if (OldOutCount != BBS->AvailableOut.size()) {
assert(OldOutCount > BBS->AvailableOut.size() && "invariant!");
Worklist.insert(succ_begin(BB), succ_end(BB));
}
}
}
bool GCPtrTracker::removeValidUnrelocatedDefs(const BasicBlock *BB,
const BasicBlockState *BBS,
AvailableValueSet &Contribution) {
assert(&BBS->Contribution == &Contribution &&
"Passed Contribution should be from the passed BasicBlockState!");
AvailableValueSet AvailableSet = BBS->AvailableIn;
bool ContributionChanged = false;
for (const Instruction &I : *BB) {
bool ValidUnrelocatedPointerDef = false;
bool PoisonedPointerDef = false;
if (const PHINode *PN = dyn_cast<PHINode>(&I)) {
if (containsGCPtrType(PN->getType())) {
bool HasRelocatedInputs = false;
bool HasUnrelocatedInputs = false;
for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i) {
const BasicBlock *InBB = PN->getIncomingBlock(i);
if (!isMapped(InBB) ||
!CD.hasLiveIncomingEdge(PN, InBB))
continue;
const Value *InValue = PN->getIncomingValue(i);
if (isNotExclusivelyConstantDerived(InValue)) {
if (isValuePoisoned(InValue)) {
HasRelocatedInputs = true;
HasUnrelocatedInputs = true;
break;
}
if (BlockMap[InBB]->AvailableOut.count(InValue))
HasRelocatedInputs = true;
else
HasUnrelocatedInputs = true;
}
}
if (HasUnrelocatedInputs) {
if (HasRelocatedInputs)
PoisonedPointerDef = true;
else
ValidUnrelocatedPointerDef = true;
}
}
} else if ((isa<GetElementPtrInst>(I) || isa<BitCastInst>(I)) &&
containsGCPtrType(I.getType())) {
for (const Value *V : I.operands())
if (containsGCPtrType(V->getType()) &&
isNotExclusivelyConstantDerived(V) && !AvailableSet.count(V)) {
if (isValuePoisoned(V))
PoisonedPointerDef = true;
else
ValidUnrelocatedPointerDef = true;
break;
}
}
assert(!(ValidUnrelocatedPointerDef && PoisonedPointerDef) &&
"Value cannot be both unrelocated and poisoned!");
if (ValidUnrelocatedPointerDef) {
Contribution.erase(&I);
PoisonedDefs.erase(&I);
ValidUnrelocatedDefs.insert(&I);
LLVM_DEBUG(dbgs() << "Removing urelocated " << I
<< " from Contribution of " << BB->getName() << "\n");
ContributionChanged = true;
} else if (PoisonedPointerDef) {
Contribution.erase(&I);
PoisonedDefs.insert(&I);
LLVM_DEBUG(dbgs() << "Removing poisoned " << I << " from Contribution of "
<< BB->getName() << "\n");
ContributionChanged = true;
} else {
bool Cleared = false;
transferInstruction(I, Cleared, AvailableSet);
(void)Cleared;
}
}
return ContributionChanged;
}
void GCPtrTracker::gatherDominatingDefs(const BasicBlock *BB,
AvailableValueSet &Result,
const DominatorTree &DT) {
DomTreeNode *DTN = DT[const_cast<BasicBlock *>(BB)];
assert(DTN && "Unreachable blocks are ignored");
while (DTN->getIDom()) {
DTN = DTN->getIDom();
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 (BBS->Cleared)
return;
}
for (const Argument &A : BB->getParent()->args())
if (containsGCPtrType(A.getType()))
Result.insert(&A);
}
void GCPtrTracker::transferBlock(const BasicBlock *BB, BasicBlockState &BBS,
bool ContributionChanged) {
const AvailableValueSet &AvailableIn = BBS.AvailableIn;
AvailableValueSet &AvailableOut = BBS.AvailableOut;
if (BBS.Cleared) {
if (ContributionChanged)
AvailableOut = BBS.Contribution;
} else {
AvailableValueSet Temp = BBS.Contribution;
set_union(Temp, AvailableIn);
AvailableOut = std::move(Temp);
}
LLVM_DEBUG(dbgs() << "Transfered block " << BB->getName() << " from ";
PrintValueSet(dbgs(), AvailableIn.begin(), AvailableIn.end());
dbgs() << " to ";
PrintValueSet(dbgs(), AvailableOut.begin(), AvailableOut.end());
dbgs() << "\n";);
}
void GCPtrTracker::transferInstruction(const Instruction &I, bool &Cleared,
AvailableValueSet &Available) {
if (isa<GCStatepointInst>(I)) {
Cleared = true;
Available.clear();
} else if (containsGCPtrType(I.getType()))
Available.insert(&I);
}
void InstructionVerifier::verifyInstruction(
const GCPtrTracker *Tracker, const Instruction &I,
const AvailableValueSet &AvailableSet) {
if (const PHINode *PN = dyn_cast<PHINode>(&I)) {
if (containsGCPtrType(PN->getType()))
for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i) {
const BasicBlock *InBB = PN->getIncomingBlock(i);
const BasicBlockState *InBBS = Tracker->getBasicBlockState(InBB);
if (!InBBS ||
!Tracker->hasLiveIncomingEdge(PN, InBB))
continue;
const Value *InValue = PN->getIncomingValue(i);
if (isNotExclusivelyConstantDerived(InValue) &&
!InBBS->AvailableOut.count(InValue))
reportInvalidUse(*InValue, *PN);
}
} else if (isa<CmpInst>(I) &&
containsGCPtrType(I.getOperand(0)->getType())) {
Value *LHS = I.getOperand(0), *RHS = I.getOperand(1);
enum BaseType baseTyLHS = getBaseType(LHS),
baseTyRHS = getBaseType(RHS);
auto hasValidUnrelocatedUse = [&AvailableSet, Tracker, baseTyLHS, baseTyRHS,
&LHS, &RHS] () {
if (AvailableSet.count(LHS) || AvailableSet.count(RHS))
return false;
if ((baseTyLHS == BaseType::ExclusivelySomeConstant &&
baseTyRHS == BaseType::NonConstant) ||
(baseTyLHS == BaseType::NonConstant &&
baseTyRHS == BaseType::ExclusivelySomeConstant))
return false;
if ((Tracker->isValuePoisoned(LHS) && baseTyRHS != ExclusivelyNull) ||
(Tracker->isValuePoisoned(RHS) && baseTyLHS != ExclusivelyNull))
return false;
return true;
};
if (!hasValidUnrelocatedUse()) {
if (baseTyLHS == BaseType::NonConstant && !AvailableSet.count(LHS))
reportInvalidUse(*LHS, I);
if (baseTyRHS == BaseType::NonConstant && !AvailableSet.count(RHS))
reportInvalidUse(*RHS, I);
}
} else {
for (const Value *V : I.operands())
if (containsGCPtrType(V->getType()) &&
isNotExclusivelyConstantDerived(V) && !AvailableSet.count(V))
reportInvalidUse(*V, I);
}
}
void InstructionVerifier::reportInvalidUse(const Value &V,
const Instruction &I) {
errs() << "Illegal use of unrelocated value found!\n";
errs() << "Def: " << V << "\n";
errs() << "Use: " << I << "\n";
if (!PrintOnly)
abort();
AnyInvalidUses = true;
}
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, CD);
InstructionVerifier Verifier;
GCPtrTracker::verifyFunction(std::move(Tracker), Verifier);
if (PrintOnly && !Verifier.hasAnyInvalidUses()) {
dbgs() << "No illegal uses found by SafepointIRVerifier in: " << F.getName()
<< "\n";
}
}