Files
revng-revng/reachingdefinitions.cpp
T
2017-08-12 16:56:23 +02:00

1232 lines
39 KiB
C++

/// \file reachingdefinitions.cpp
/// \brief Implementation of the ReachingDefinitionsPass
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
// Standard includes
#include <array>
#include <cstdint>
#include <iomanip>
#include <map>
#include <set>
#include <unordered_map>
#include <vector>
// LLVM includes
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/Casting.h"
// Local includes
#include "datastructures.h"
#include "debug.h"
#include "functioncallidentification.h"
#include "ir-helpers.h"
#include "reachingdefinitions.h"
using namespace llvm;
using std::pair;
using std::queue;
using std::set;
using std::tie;
using std::unordered_map;
using std::vector;
using IndexesVector = SmallVector<int32_t, 2>;
template<class BBI, ReachingDefinitionsResult R>
const vector<LoadInst *> &
ReachingDefinitionsImplPass<BBI, R>::getReachedLoads(const Instruction *Definition) {
assert(R == ReachingDefinitionsResult::ReachedLoads);
return ReachedLoads[Definition];
}
template<class BBI, ReachingDefinitionsResult R>
const vector<Instruction *> &
ReachingDefinitionsImplPass<BBI, R>::getReachingDefinitions(const LoadInst *Load) {
return ReachingDefinitions[Load];
}
template<class B, ReachingDefinitionsResult R>
unsigned
ReachingDefinitionsImplPass<B, R>::getReachingDefinitionsCount(const LoadInst *Load) {
assert(R == ReachingDefinitionsResult::ReachedLoads);
return ReachingDefinitionsCount[Load];
}
using RDP = ReachingDefinitionsResult;
template class ReachingDefinitionsImplPass<BasicBlockInfo,
RDP::ReachingDefinitions>;
template class ReachingDefinitionsImplPass<BasicBlockInfo,
RDP::ReachedLoads>;
template<class BBI, ReachingDefinitionsResult R>
char ReachingDefinitionsImplPass<BBI, R>::ID = 0;
template<>
char ReachingDefinitionsImplPass<BasicBlockInfo, ReachingDefinitionsResult::ReachingDefinitions>::ID = 0;
template<>
char ReachingDefinitionsImplPass<ConditionalBasicBlockInfo, ReachingDefinitionsResult::ReachedLoads>::ID = 0;
static RegisterPass<ReachingDefinitionsPass> X1("rdp",
"Reaching Definitions Pass",
true,
true);
static RegisterPass<ReachedLoadsPass> X2("rlp",
"Reaching Definitions Pass",
true,
true);
// ReachingDefinitionsPass methods implementation
template<>
const IndexesVector &
ReachingDefinitionsPass::getDefinedConditions(BasicBlock *BB) {
return ConditionNumberingPass::NoDefinedConditions;
}
template<>
int32_t ReachingDefinitionsPass::getConditionIndex(TerminatorInst *V) {
return 0;
}
template<>
void ReachingDefinitionsPass::getAnalysisUsage(AnalysisUsage &AU) const {
AU.setPreservesAll();
AU.addRequired<FunctionCallIdentification>();
}
// ReachedLoadsPass methods implementations
template<>
const IndexesVector &
ReachedLoadsPass::getDefinedConditions(BasicBlock *BB) {
return ConditionNumberingPass::NoDefinedConditions;
}
template<>
int32_t ReachedLoadsPass::getConditionIndex(TerminatorInst *V) {
return 0;
}
template<>
void ReachedLoadsPass::getAnalysisUsage(AnalysisUsage &AU) const {
AU.setPreservesAll();
AU.addRequired<FunctionCallIdentification>();
}
template class ReachingDefinitionsImplPass<ConditionalBasicBlockInfo,
RDP::ReachingDefinitions>;
template class ReachingDefinitionsImplPass<ConditionalBasicBlockInfo,
RDP::ReachedLoads>;
static RegisterPass<ConditionalReachingDefinitionsPass> Y1("crdp",
"Conditional"
" Reaching"
" Definitions Pass",
true,
true);
static RegisterPass<ConditionalReachedLoadsPass> Y2("crlp",
"Conditional"
" Reaching"
" Definitions Pass",
true,
true);
// ConditionalReachingDefinitionsPass methods implementations
template<>
const IndexesVector &
ConditionalReachingDefinitionsPass::getDefinedConditions(BasicBlock *BB) {
return getAnalysis<ConditionNumberingPass>().getDefinedConditions(BB);
}
// TODO: this duplication sucks
template<>
int32_t
ConditionalReachingDefinitionsPass::getConditionIndex(TerminatorInst *T) {
auto *Branch = dyn_cast<BranchInst>(T);
if (Branch == nullptr || !Branch->isConditional())
return 0;
return getAnalysis<ConditionNumberingPass>().getConditionIndex(T);
}
template<>
void
ConditionalReachingDefinitionsPass::getAnalysisUsage(AnalysisUsage &AU) const {
AU.setPreservesAll();
AU.addRequired<ConditionNumberingPass>();
AU.addRequired<FunctionCallIdentification>();
}
template<>
int32_t
ConditionalReachedLoadsPass::getConditionIndex(TerminatorInst *T) {
auto *Branch = dyn_cast<BranchInst>(T);
if (Branch == nullptr || !Branch->isConditional())
return 0;
return getAnalysis<ConditionNumberingPass>().getConditionIndex(T);
}
// ConditionalReachedLoadsPass methods implementation
template<>
const IndexesVector &
ConditionalReachedLoadsPass::getDefinedConditions(BasicBlock *BB) {
return getAnalysis<ConditionNumberingPass>().getDefinedConditions(BB);
}
template<>
void
ConditionalReachedLoadsPass::getAnalysisUsage(AnalysisUsage &AU) const {
AU.setPreservesAll();
AU.addRequired<ConditionNumberingPass>();
AU.addRequired<FunctionCallIdentification>();
}
static size_t combine(size_t A, size_t B) {
return (A << 1 | A >> 31) ^ B;
}
static size_t combine(size_t A, void *Ptr) {
return combine(A, reinterpret_cast<intptr_t>(Ptr));
}
static bool isSupportedOperator(unsigned Opcode) {
switch (Opcode) {
case Instruction::Xor:
case Instruction::And:
case Instruction::Or:
case Instruction::ICmp:
return true;
default:
return false;
}
}
class ConditionHash {
public:
ConditionHash(ReachingDefinitionsPass &RDP) : RDP(RDP) { }
size_t operator()(BranchInst * const& V) const;
private:
ReachingDefinitionsPass &RDP;
};
size_t ConditionHash::operator()(BranchInst * const& B) const {
Value *V = B->getCondition();
size_t Hash = 0;
queue<Value *> WorkList;
WorkList.push(V);
while (!WorkList.empty()) {
Value *V;
V = WorkList.front();
WorkList.pop();
bool IsStore = isa<StoreInst>(V);
bool IsLoad = isa<LoadInst>(V);
if (IsStore || IsLoad) {
// Load/store vs load/store
if (IsStore) {
Hash = combine(Hash, cast<StoreInst>(V)->getPointerOperand());
} else {
for (Instruction *I : RDP.getReachingDefinitions(cast<LoadInst>(V))) {
if (auto *Store = dyn_cast<StoreInst>(I))
Hash = combine(Hash, Store->getPointerOperand());
else if (auto *Load = dyn_cast<LoadInst>(I))
Hash = combine(Hash, Load->getPointerOperand());
}
}
} else if (auto *I = dyn_cast<Instruction>(V)) {
// Instruction
if (!isSupportedOperator(I->getOpcode())) {
Hash = combine(Hash, V);
} else {
Hash = combine(Hash, I->getOpcode());
Hash = combine(Hash, I->getNumOperands());
for (unsigned C = 0; C < I->getNumOperands(); C++)
WorkList.push(I->getOperand(C));
}
} else {
Hash = combine(Hash, V);
}
}
return Hash;
}
class ConditionEqualTo {
public:
ConditionEqualTo(ReachingDefinitionsPass &RDP) : RDP(RDP) { }
bool operator()(BranchInst * const& A, BranchInst * const& B) const;
private:
ReachingDefinitionsPass &RDP;
};
bool ConditionEqualTo::operator()(BranchInst * const& BA,
BranchInst * const& BB) const {
Value *A = BA->getCondition();
Value *B = BB->getCondition();
queue<pair<Value *, Value *>> WorkList;
WorkList.push({A, B});
while (!WorkList.empty()) {
Value *AV, *BV;
tie(AV, BV) = WorkList.front();
WorkList.pop();
// Early continue in case they're exactly the same value
if (AV == BV)
continue;
bool AIsStore = isa<StoreInst>(AV);
bool AIsLoad = isa<LoadInst>(AV);
bool BIsStore = isa<StoreInst>(BV);
bool BIsLoad = isa<LoadInst>(BV);
if ((AIsStore || AIsLoad) && (BIsStore || BIsLoad)) {
// Load/store vs load/store
vector<Instruction *> AStores;
if (AIsStore)
AStores.push_back(cast<StoreInst>(AV));
else
AStores = RDP.getReachingDefinitions(cast<LoadInst>(AV));
vector<Instruction *> BStores;
if (BIsStore)
BStores.push_back(cast<StoreInst>(BV));
else
BStores = RDP.getReachingDefinitions(cast<LoadInst>(BV));
if (AStores != BStores)
return false;
} else if (auto *AI = dyn_cast<Instruction>(AV)) {
// Instruction
auto *BI = dyn_cast<Instruction>(BV);
if (BI == nullptr
|| AI->getOpcode() != BI->getOpcode()
|| AI->getNumOperands() != BI->getNumOperands()
|| !isSupportedOperator(AI->getOpcode()))
return false;
for (unsigned I = 0; I < AI->getNumOperands(); I++)
WorkList.push({ AI->getOperand(I), BI->getOperand(I) });
} else {
return false;
}
}
return true;
}
static SmallSet<BasicBlock *, 2>
resettingBasicBlocks(ReachingDefinitionsPass &RDP, BranchInst * const& Branch) {
SmallSet<BasicBlock *, 2> Result;
Value *A = Branch->getCondition();
queue<Value *> WorkList;
WorkList.push(A);
while (!WorkList.empty()) {
Value *AV;
AV = WorkList.front();
WorkList.pop();
bool AIsStore = isa<StoreInst>(AV);
bool AIsLoad = isa<LoadInst>(AV);
if (AIsStore || AIsLoad) {
// Load/store vs load/store
vector<Instruction *> AStores;
if (AIsStore) {
Result.insert(cast<StoreInst>(AV)->getParent());
} else {
for (Instruction *I : RDP.getReachingDefinitions(cast<LoadInst>(AV))) {
Result.insert(I->getParent());
}
}
} else if (auto *AI = dyn_cast<Instruction>(AV)) {
// Instruction
if (!isSupportedOperator(AI->getOpcode()))
return { };
for (unsigned I = 0; I < AI->getNumOperands(); I++)
WorkList.push(AI->getOperand(I));
} else if (!isa<Constant>(AV)) {
return { };
}
}
return Result;
}
char ConditionNumberingPass::ID = 0;
const IndexesVector ConditionNumberingPass::NoDefinedConditions;
static RegisterPass<ConditionNumberingPass> Z("cnp",
"Condition Numbering Pass",
true,
true);
template<typename C, typename T>
static bool pushIfAbsent(C &Container, T Element) {
auto It = std::find(Container.begin(), Container.end(), Element);
bool Result = It != Container.end();
if (!Result)
Container.push_back(Element);
return Result;
}
/// \brief Support class for easily adding edges on the CFG using switch
/// instructions.
///
/// FakeSwitch creates a SwitchInst to which the user can easily add cases,
/// without caring about the label value. Moreover, FakeSwitch automatically
/// backups and replaces the terminator instruction, if present, adds its
/// successors to the switch, and, when restore is called, restore it.
class FakeSwitch {
public:
FakeSwitch(BasicBlock *Target, unsigned NumCases) :
Target(Target), SavedTerminator(nullptr), Switch(nullptr),
Ty(IntegerType::get(getContext(Target), 32)), NumCases(NumCases) {
SavedTerminator = Target->getTerminator();
if (SavedTerminator != nullptr)
this->NumCases += SavedTerminator->getNumSuccessors();
}
void add(BasicBlock *New) {
// Is this the first basic block being added? If so, create the switch and
// detach the old terminator instruction.
if (Switch == nullptr) {
// Create the switch statement and append it to the basic block
Switch = SwitchInst::Create(ConstantInt::get(Ty, 0),
New,
NumCases,
Target);
// If there was a terminator save it and add all its successors to the
// switch
if (SavedTerminator != nullptr) {
SavedTerminator->removeFromParent();
for (BasicBlock *Successor : SavedTerminator->successors()) {
// Note: this will never cause infinite recursion since we just
// initialized the Switch field
add(Successor);
}
}
}
// Add the requested basic block
Switch->addCase(ConstantInt::get(Ty, Switch->getNumCases() + 1), New);
}
void restore() {
// Check if we ever did anything
if (Switch == nullptr)
return;
// We no longer need the switch
Switch->eraseFromParent();
// Restore the old terminator
if (SavedTerminator != nullptr) {
Target->getInstList().push_back(SavedTerminator);
assert(Target->getTerminator() == SavedTerminator);
}
}
private:
BasicBlock *Target;
TerminatorInst *SavedTerminator;
SwitchInst *Switch;
IntegerType *Ty;
unsigned NumCases;
};
bool ConditionNumberingPass::runOnFunction(Function &F) {
DBG("passes", { dbg << "Starting ConditionNumberingPass\n"; });
LLVMContext &C = F.getParent()->getContext();
auto &RDP = getAnalysis<ReachingDefinitionsPass>();
unordered_map<BranchInst *,
SmallVector<BranchInst *, 1>,
ConditionHash,
ConditionEqualTo> Conditions(10,
ConditionHash(RDP),
ConditionEqualTo(RDP));
// Group conditions together
for (BasicBlock &BB : F)
if (auto *Branch = dyn_cast<BranchInst>(BB.getTerminator()))
if (Branch->isConditional())
Conditions[Branch].push_back(Branch);
// Save the interesting results
uint32_t ConditionIndex = 0;
// Initialize the vector of predecessors of BBs sharing the same condition
using BB = BasicBlock;
std::vector<BasicBlock *> CommonPredecessors;
// Debugging purposes only
std::map<uint32_t, SmallVector<BasicBlock *, 2>> ResettingBasicBlocks;
for (auto &P : Conditions) {
// Ignore all the conditions present in a single branch
if (P.second.size() > 1) {
// 0 is a reserved value, since it doesn't have a corresponding negative
// value
ConditionIndex++;
// Create the common predecessor and register it
auto *CommonPredecessor = BB::Create(C, "cp" + Twine(ConditionIndex), &F);
CommonPredecessors.push_back(CommonPredecessor);
// Create the fake switch which will create the edges from the common
// predecessor to all the basic blocks containing the branches associated
// with this condition
FakeSwitch Switch(CommonPredecessor, P.second.size());
for (BranchInst *B : P.second) {
// Build the branch -> condition index mapping
BranchConditionNumberMap[B] = ConditionIndex;
// Build the list of conditions defined by each basic block
for (BasicBlock *Definer : resettingBasicBlocks(RDP, B)) {
// Register that Definer defines ConditionIndex
pushIfAbsent(DefinedConditions[Definer], ConditionIndex);
// Register that ConditionIndex is defined by Defined
DBG("cnp", {
pushIfAbsent(ResettingBasicBlocks[ConditionIndex], Definer);
});
}
// Add an edge from the common predecessor to this basic block
Switch.add(B->getParent());
}
DBG("cnp",
{
dbg << std::dec << ConditionIndex << ":";
for (BranchInst *B : P.second)
dbg << " " << getName(B);
auto It = P.second.begin();
if (It != P.second.end()) {
dbg << " (defined by:";
for (BasicBlock *Definer : resettingBasicBlocks(RDP, *It)) {
dbg << " " << getName(Definer);
}
dbg << ")";
}
dbg << "\n";
});
}
}
// Make each common predecessor reachable from the entry point, so that the
// PDT can take them into account.
FakeSwitch EntrySwitch(&F.getEntryBlock(), CommonPredecessors.size());
for (BasicBlock *CommonPredecessor : CommonPredecessors)
EntrySwitch.add(CommonPredecessor);
// Compute the post-dominator tree
DominatorTreeBase<BasicBlock> PDT(true);
PDT.recalculate(F);
// Get the immediate post-dominator of each temporary basic block and then
// delete it
for (unsigned I = 0; I < CommonPredecessors.size(); I++) {
BasicBlock *CommonPredecessor = CommonPredecessors[I];
DBG("cnp", {
dbg << "Condition index " << (I + 1) << " (";
for (BasicBlock *Successor : successors(CommonPredecessor))
dbg << getName(Successor) << " ";
dbg << ")";
dbg << ", defined by";
for (BasicBlock *Defined : ResettingBasicBlocks[I + 1])
dbg << " " << getName(Defined);
});
// Get the immediate post-dominator of the common predecessor
auto *PDTNode = PDT.getNode(CommonPredecessor);
// Check if it's reachable from the exit (i.e., it's not part of an infinite
// loop).
BasicBlock *ImmediatePostDominator = nullptr;
// TODO: for some reason getBlock() might give nullptr, investigate
if (PDTNode != nullptr)
ImmediatePostDominator = PDTNode->getIDom()->getBlock();
if (ImmediatePostDominator != nullptr) {
// Add the current ConditionIndex to those defined by it
// Note: ConditionIndex 0 is reserved, so we add one
for (BasicBlock *Successor : successors(ImmediatePostDominator))
pushIfAbsent(DefinedConditions[Successor], I + 1);
DBG("cnp", {
dbg << ", post-dominated by "
<< getName(ImmediatePostDominator) << "\n";
});
} else {
DBG("cnp", dbg << ", no post dominator\n");
}
}
// Restore the entry block's terminator instruction
EntrySwitch.restore();
// Delete all the common predecessor basic blocks, we no longer need them
for (BasicBlock *CommonPredecessor : CommonPredecessors)
CommonPredecessor->eraseFromParent();
DBG("passes", { dbg << "Ending ConditionNumberingPass\n"; });
return false;
}
void BasicBlockInfo::dump(std::ostream &Output) {
set<Instruction *> Printed;
for (const MemoryInstruction &MI : Reaching) {
Instruction *V = MI.I;
if (Printed.count(V) == 0) {
Printed.insert(V);
Output << " " << getName(V);
}
}
}
void BasicBlockInfo::newDefinition(StoreInst *Store, TypeSizeProvider &TSP) {
// Remove all the aliased reaching definitions
MemoryAccess TargetMA(Store, TSP);
removeDefinitions([&TargetMA] (MemoryInstruction &MI) {
return TargetMA.mayAlias(MI.MA);
});
// Add this definition
Definitions.push_back(MemoryInstruction(Store, TSP));
}
LoadDefinitionType BasicBlockInfo::newDefinition(LoadInst *Load,
TypeSizeProvider &TSP) {
LoadDefinitionType Result = NoReachingDefinitions;
// Check if it's a self-referencing load
MemoryAccess TargetMA(Load, TSP);
for (auto &MI : Definitions) {
auto *Definition = MI.I;
if (Definition == Load) {
// It's self-referencing, suppress all the matching loads
removeDefinitions([&TargetMA] (MemoryInstruction &MI) {
return isa<LoadInst>(MI.I) && TargetMA == MI.MA;
});
Result = SelfReaching;
break;
} else if (TargetMA == MI.MA) {
Result = HasReachingDefinitions;
}
}
// Add this definition
if (Result == NoReachingDefinitions)
Definitions.push_back(MemoryInstruction(Load, TSP));
return Result;
}
bool BasicBlockInfo::propagateTo(BasicBlockInfo &Target,
TypeSizeProvider &TSP,
const IndexesVector &,
int32_t NewConditionIndex) {
bool Changed = false;
for (MemoryInstruction &Definition : Definitions)
Changed |= Target.Reaching.insert(Definition).second;
return Changed;
}
vector<pair<Instruction *, MemoryAccess>>
BasicBlockInfo::getReachingDefinitions(set<LoadInst *> &WhiteList,
TypeSizeProvider &TSP) {
vector<pair<Instruction *, MemoryAccess>> Result;
for (const MemoryInstruction &MI : Reaching) {
Instruction *I = MI.I;
if (auto *Load = dyn_cast<LoadInst>(I)) {
// If it's a load check it's whitelisted
if (WhiteList.count(Load) != 0)
Result.push_back({ Load, MI.MA });
} else {
// It's a store
Result.push_back({ I, MI.MA });
}
}
freeContainer(Reaching);
assert(Reaching.size() == 0);
return Result;
}
void ConditionalBasicBlockInfo::dump(std::ostream& Output) {
set<Instruction *> Printed;
for (auto &P : Reaching) {
Instruction *I = P.first.I;
if (Printed.count(I) == 0) {
Printed.insert(I);
Output << " " << getName(I);
}
}
}
void ConditionalBasicBlockInfo::newDefinition(StoreInst *Store,
TypeSizeProvider &TSP) {
// Remove all the aliased reaching definitions
MemoryAccess TargetMA(Store, TSP);
removeDefinitions([&TargetMA] (CondDefPair &P) {
// TODO: don't erase if conditions are complementary
return TargetMA.mayAlias(P.second.MA);
});
// Perform the merge
// Note that the new definition absorbes all the conditions holding in the
// current basic block
mergeDefinition({ Conditions, MemoryInstruction(Store, TSP) },
Definitions,
TSP);
}
LoadDefinitionType
ConditionalBasicBlockInfo::newDefinition(LoadInst *Load,
TypeSizeProvider &TSP) {
LoadDefinitionType Result = NoReachingDefinitions;
// Check if it's a self-referencing load
MemoryAccess TargetMA(Load, TSP);
for (auto &P : Definitions) {
auto *Definition = P.second.I;
if (Definition == Load) {
// It's self-referencing, suppress all the matching loads
removeDefinitions([&TargetMA] (CondDefPair &P) {
// TODO: can we embed if it's a load or a store in
// MemoryInstruction?
return isa<LoadInst>(P.second.I) && P.second.MA == TargetMA;
});
Result = SelfReaching;
break;
} else if (TargetMA == P.second.MA) {
Result = HasReachingDefinitions;
}
}
// Add this definition
if (Result == NoReachingDefinitions)
mergeDefinition({ Conditions, MemoryInstruction(Load, TSP) },
Definitions,
TSP);
return Result;
}
vector<pair<Instruction *, MemoryAccess>>
ConditionalBasicBlockInfo::getReachingDefinitions(set<LoadInst *> &WhiteList,
TypeSizeProvider &TSP) {
vector<pair<Instruction *, MemoryAccess>> Result;
for (auto &P : Reaching) {
Instruction *I = P.first.I;
if (auto *Load = dyn_cast<LoadInst>(I)) {
// If it's a load check it's whitelisted
if (WhiteList.count(Load) != 0)
Result.push_back({ Load, P.first.MA });
} else {
// It's a store
Result.push_back({ I, P.first.MA });
}
}
freeContainer(Reaching);
return Result;
}
bool ConditionalBasicBlockInfo::setIndexIfSeen(BitVector &Target,
int32_t Index) const {
auto ConditionIt = std::find(SeenConditions.begin(),
SeenConditions.end(),
Index);
// If present set the corresponding bit in Defined
if (ConditionIt != SeenConditions.end()) {
Target.set(ConditionIt - SeenConditions.begin());
return true;
}
return false;
}
bool
ConditionalBasicBlockInfo::propagateTo(ConditionalBasicBlockInfo &Target,
TypeSizeProvider &TSP,
const IndexesVector &DefinedIndexes,
int32_t NewConditionIndex) {
bool Changed = false;
// Get (and insert, if necessary) the bit associated to the new
// condition. This bit will be set in all the definitions being propagated.
DBG("rdp-propagation", dbg << " Adding conditions:");
unsigned NewConditionBitIndex = Target.getConditionIndex(NewConditionIndex);
if (NewConditionIndex != 0 && !Target.Conditions[NewConditionBitIndex]) {
Target.Conditions.set(NewConditionBitIndex);
DBG("rdp-propagation", dbg << " " << NewConditionIndex);
Changed = true;
}
// Condition propagation
for (int SetBitIndex = Conditions.find_first();
SetBitIndex != -1;
SetBitIndex = Conditions.find_next(SetBitIndex)) {
int32_t ToPropagate = SeenConditions[SetBitIndex];
// Do not propagate the condition if:
//
// * it's defined in the target basic block
// * it's the condition associated to the current branch
// * the target basic block already has it
//
auto It = std::find_if(DefinedIndexes.begin(),
DefinedIndexes.end(),
[ToPropagate] (int32_t Defined) {
return Defined == ToPropagate
|| Defined == -ToPropagate;
});
if (ToPropagate != NewConditionIndex
&& ToPropagate != -NewConditionIndex
&& It == DefinedIndexes.end()
&& !Target.hasCondition(ToPropagate)) {
Target.addCondition(ToPropagate);
DBG("rdp-propagation", dbg << " " << ToPropagate);
Changed = true;
}
}
DBG("rdp-propagation", dbg << "\n");
// Compute a bit vector with all the conditions that are incompatible with the
// target
BitVector Banned(SeenConditions.size());
DBG("rdp-propagation", dbg << " Banned conditions:");
// For each set bit in the target's conditions
for (int SetBitIndex = Target.Conditions.find_first();
SetBitIndex != -1;
SetBitIndex = Target.Conditions.find_next(SetBitIndex)) {
// Consider the opposite condition as banned
int32_t BannedIndex = -Target.SeenConditions[SetBitIndex];
DBG("rdp-propagation", dbg << " " << BannedIndex);
// Check BannedIndex is not explicitly allowed
auto BannedIt = std::find(Target.SeenConditions.begin(),
Target.SeenConditions.end(),
BannedIndex);
bool IsAllowed = BannedIt != Target.SeenConditions.end()
&& Target.Conditions[BannedIt - Target.SeenConditions.begin()];
if (!IsAllowed)
setIndexIfSeen(Banned, BannedIndex);
}
DBG("rdp-propagation", dbg << "\n");
// Create a BitVector for conditions defined in the target basic block, so
// that we can later exclude them
BitVector Defined(SeenConditions.size());
for (int32_t DefinedIndex : DefinedIndexes) {
setIndexIfSeen(Defined, DefinedIndex);
setIndexIfSeen(Defined, -DefinedIndex);
}
BitVector NotDefined = Defined;
NotDefined.flip();
for (auto &Definition : Definitions) {
BitVector DefinitionConditions = Definition.first;
DBG("rdp-propagation", {
dbg << " Propagate " << getName(Definition.second.I);
if (auto *Load = dyn_cast<LoadInst>(Definition.second.I))
dbg << " about " << Load->getPointerOperand()->getName().str();
else if (auto *Store = dyn_cast<StoreInst>(Definition.second.I))
dbg << " about " << Store->getPointerOperand()->getName().str();
if (DefinitionConditions.any()) {
dbg << " (conditions:";
for (int I = DefinitionConditions.find_first();
I != -1;
I = DefinitionConditions.find_next(I)) {
dbg << " " << SeenConditions[I];
}
dbg << ")";
}
dbg << "? ";
});
// Reset all the conditions that are defined in the target basic block
DefinitionConditions &= NotDefined;
// Check if this definition is compatible with the target basic block
auto BannedConditions = DefinitionConditions;
BannedConditions &= Banned;
if (BannedConditions.any()) {
DBG("rdp-propagation", dbg << "no\n");
continue;
}
DBG("rdp-propagation", dbg << "yes");
// Translate the conditions bitvector to the context of the target BBI
BitVector Translated(Target.SeenConditions.size());
for (int I = DefinitionConditions.find_first();
I != -1;
I = DefinitionConditions.find_next(I)) {
// Make sure the target BBI knows about all the necessary conditinos
assert(I < static_cast<int>(SeenConditions.size()));
unsigned Index = Target.getConditionIndex(SeenConditions[I]);
unsigned OppositeIndex = Target.getConditionIndex(-SeenConditions[I]);
// Keep the size of the new bitvector in sync
if (Target.SeenConditions.size() != Translated.size())
Translated.resize(Target.SeenConditions.size());
Translated.set(Index);
Translated.reset(OppositeIndex);
}
// Add the condition of this branch
if (NewConditionIndex != 0)
Translated.set(NewConditionBitIndex);
Changed |= Target.mergeDefinition({ Translated, Definition.second },
Target.Reaching,
TSP);
DBG("rdp-propagation", dbg << " Changed? " << Changed << "\n");
}
return Changed;
}
bool ConditionalBasicBlockInfo::mergeDefinition(CondDefPair NewDefinition,
vector<CondDefPair> &Targets,
TypeSizeProvider &TSP) const {
BitVector &NewConditionsBV = NewDefinition.first;
assert(NewConditionsBV.size() == SeenConditions.size());
for (CondDefPair &Target : Targets) {
// Does this definition matches the one we're looking for?
if (Target.second.I == NewDefinition.second.I) {
// Are we saying something new? If so, merge the conditions.
if (Target.first != NewConditionsBV) {
Target.first |= NewConditionsBV;
return true;
} else {
return false;
}
}
}
// This definition is new, register it
Targets.push_back(NewDefinition);
return true;
}
bool ConditionalBasicBlockInfo::mergeDefinition(CondDefPair NewDefinition,
ReachingType &Targets,
TypeSizeProvider &TSP) const {
BitVector &NewConditionsBV = NewDefinition.first;
assert(NewConditionsBV.size() == SeenConditions.size());
// Merge the conditions of the new definition
BitVector &BV = Targets[NewDefinition.second];
BitVector Old = BV;
BV |= NewConditionsBV;
// Check if the new conditions are different from the initial ones
return Old != BV;
}
template<class BBI, ReachingDefinitionsResult R>
bool ReachingDefinitionsImplPass<BBI, R>::runOnFunction(Function &F) {
auto &FCI = getAnalysis<FunctionCallIdentification>();
DBG("passes", {
if (std::is_same<BBI, ConditionalBasicBlockInfo>::value)
dbg << "Starting ConditionalReachingDefinitionsPass\n";
else
dbg << "Starting ReachingDefinitionsPass\n";
});
for (auto &BB : F) {
if (!BB.empty()) {
if (auto *Call = dyn_cast<CallInst>(&*BB.begin())) {
Function *Callee = Call->getCalledFunction();
// TODO: comparing with "newpc" string is sad
if (Callee != nullptr && Callee->getName() == "newpc")
break;
}
}
BasicBlockBlackList.insert(&BB);
}
TypeSizeProvider TSP(F.getParent()->getDataLayout());
// Initialize queue
unsigned BasicBlockCount = 0;
unsigned BasicBlockVisits = 0;
ReversePostOrderTraversal<Function *> RPOT(&F);
UniquedStack<BasicBlock *> ToVisit;
for (BasicBlock *BB : RPOT) {
ToVisit.insert(BB);
BasicBlockCount++;
}
ToVisit.reverse();
while (!ToVisit.empty()) {
BasicBlockVisits++;
BasicBlock *BB = ToVisit.pop();
BBI &Info = DefinitionsMap[BB];
Info.resetDefinitions(TSP);
// Find all the definitions
for (Instruction &I : *BB) {
auto *Store = dyn_cast<StoreInst>(&I);
auto *Load = dyn_cast<LoadInst>(&I);
if (Store != nullptr && MemoryAccess(Store, TSP).isValid()) {
// Record new definition
Info.newDefinition(Store, TSP);
} else if (Load != nullptr && MemoryAccess(Load, TSP).isValid()) {
// Check if it's a new definition and record it
auto LoadType = Info.newDefinition(Load, TSP);
switch (LoadType) {
case NoReachingDefinitions:
NRDLoads.insert(Load);
break;
case SelfReaching:
SelfReachingLoads.insert(Load);
break;
case HasReachingDefinitions:
NRDLoads.erase(Load);
break;
}
}
}
// TODO: this is an hack and should be replaced once we integrate calling
// convention and call graph in the basic block harvesting process
unsigned SuccessorsCount = succ_end(BB) - succ_begin(BB);
unsigned Size = Info.size();
if (!FCI.isCall(BB) && Size * SuccessorsCount <= 5000) {
// Get the identifier of the conditional instruction
int32_t ConditionIndex = getConditionIndex(BB->getTerminator());
assert(ConditionIndex == 0 || ConditionIndex > 0);
// Propagate definitions to successors, checking if actually we changed
// something, and if so re-enqueue them
for (BasicBlock *Successor : successors(BB)) {
if (BasicBlockBlackList.count(Successor) != 0)
continue;
const IndexesVector &DefinedConditions =
getDefinedConditions(Successor);
BBI &SuccessorInfo = DefinitionsMap[Successor];
DBG("rdp-propagation", {
dbg << "Propagating from " << getName(BB)
<< " to " << getName(Successor);
if (DefinedConditions.size() > 0) {
dbg << " (resetting conditions: ";
for (int32_t ConditionIndex : DefinedConditions)
dbg << " " << ConditionIndex;
dbg << ")";
}
if (ConditionIndex != 0)
dbg << ", using a " << ConditionIndex << " branch"
<< " (" << getName(BB->getTerminator()) << ")";
dbg << "\n";
});
// Enqueue the successor only if the propagation actually did something
unsigned Old = SuccessorInfo.size();
if (Info.propagateTo(SuccessorInfo,
TSP,
DefinedConditions,
ConditionIndex))
ToVisit.insert(Successor);
DBG("rdp-propagation",
dbg << getName(Successor) << std::dec
<< " got " << (SuccessorInfo.size() - Old) << " new reachers "
<< "from " << getName(BB) << " (had " << Old << ")\n");
// Add the condition relative to the current branch instruction (if any)
if (ConditionIndex != 0) {
// If ConditionIndex is positive we're in the true branch, prepare
// ConditionIndex for the false branch
if (ConditionIndex > 0)
ConditionIndex = -ConditionIndex;
}
}
// We no longer need to keep track of the definitions
Info.clearDefinitions();
}
}
// Collect final information
std::set<LoadInst *> &FreeLoads = NRDLoads;
FreeLoads.insert(SelfReachingLoads.begin(), SelfReachingLoads.end());
for (auto &P : DefinitionsMap) {
BasicBlock *BB = P.first;
BBI &Info = P.second;
// TODO: use a list?
vector<pair<Instruction *, MemoryAccess>> Definitions;
Definitions = Info.getReachingDefinitions(FreeLoads, TSP);
for (Instruction &I : *BB) {
auto *Store = dyn_cast<StoreInst>(&I);
auto *Load = dyn_cast<LoadInst>(&I);
using IMP = pair<Instruction *, MemoryAccess>;
if (Store != nullptr) {
// Remove all the reaching definitions aliased by this store
MemoryAccess TargetMA(Store, TSP);
if (!TargetMA.isValid())
continue;
erase_if(Definitions, [&TargetMA] (IMP &P) {
return TargetMA.mayAlias(P.second);
});
Definitions.push_back({ Store, TargetMA });
} else if (Load != nullptr) {
// Record all the relevant reaching defininitions
MemoryAccess TargetMA(Load, TSP);
if (!TargetMA.isValid())
continue;
if (FreeLoads.count(Load) != 0) {
// If it's a free load, remove all the matching loads
erase_if(Definitions, [&TargetMA, &TSP] (IMP &P) {
Instruction *I = P.first;
return isa<LoadInst>(I) && MemoryAccess(I, TSP) == TargetMA;
});
Definitions.push_back({ Load, TargetMA });
} else {
if (R == ReachingDefinitionsResult::ReachedLoads) {
for (auto &Definition : Definitions) {
if (TargetMA == Definition.second) {
ReachedLoads[Definition.first].push_back(Load);
ReachingDefinitionsCount[Load]++;
}
}
}
std::vector<Instruction *> LoadDefinitions;
for (auto &Definition : Definitions)
if (TargetMA == Definition.second)
LoadDefinitions.push_back(Definition.first);
// Save them in ReachingDefinitions
std::sort(LoadDefinitions.begin(), LoadDefinitions.end());
DBG("rdp",
{
dbg << getName(Load) << " is reached by:";
for (auto *Definition : LoadDefinitions)
dbg << " " << getName(Definition);
dbg << "\n";
});
ReachingDefinitions[Load] = std::move(LoadDefinitions);
}
}
}
}
DBG("rdp",
{
dbg << "Basic blocks: " << std::dec << BasicBlockCount << "\n"
<< "Visited: " << std::dec << BasicBlockVisits << "\n"
<< "Average visits per basic block: " << std::setprecision(2)
<< float(BasicBlockVisits) / BasicBlockCount << "\n";
});
if (R == ReachingDefinitionsResult::ReachedLoads) {
DBG("rdp",
for (auto P : ReachedLoads) {
dbg << getName(P.first) << " reaches";
for (auto *Load : P.second)
dbg << " " << getName(Load);
dbg << "\n";
});
}
// Clear all the temporary data that is not part of the analysis result
freeContainer(DefinitionsMap);
freeContainer(FreeLoads);
freeContainer(BasicBlockBlackList);
freeContainer(NRDLoads);
freeContainer(SelfReachingLoads);
DBG("passes", {
if (std::is_same<BBI, ConditionalBasicBlockInfo>::value)
dbg << "Ending ConditionalReachingDefinitionsPass\n";
else
dbg << "Ending ReachingDefinitionsPass\n";
});
return false;
}