mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
4acae6beed
This commit handles two cases related to function calls where we want to limit the propagation of reaching definitions. In the first case down through function calls, in the second case back up through return instructions. For the call instructions, we choose to stop the propagation of reaching definitions to the callee, since we assume each function should check its arguments if they affect the control-flow. In particular, this allows a larger coverage of the function body in case, being able to enumerate all the calls, we consider dead code those parts that, in the current program would nevere be executed. Right now we do it in all cases, it would be more appropriate to do this only if the address of the function is taken. Also, we should expand this also for tail calls. For what concerns return instructions, a function called from a lot of different locations in the code receives a huge number of reaching definitions. If its close to a no-op, it will also propagate most of them through the return path. This is an hack to limit how such definitions spread around the code. A proper solution, requires to detect the calling convention and allow to propagate along return paths only return values.
915 lines
28 KiB
C++
915 lines
28 KiB
C++
/// \file
|
|
/// \brief Implementation of the ReachingDefinitionsPass
|
|
|
|
// 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/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 "ir-helpers.h"
|
|
#include "reachingdefinitions.h"
|
|
|
|
// #include "valgrind/callgrind.h"
|
|
|
|
using namespace llvm;
|
|
|
|
using std::pair;
|
|
using std::queue;
|
|
using std::set;
|
|
using std::tie;
|
|
using std::unordered_map;
|
|
using std::vector;
|
|
|
|
template<class BBI, ReachingDefinitionsResult R>
|
|
const vector<LoadInst *> &
|
|
ReachingDefinitionsImplPass<BBI, R>::getReachedLoads(Instruction *Definition) {
|
|
assert(R == ReachingDefinitionsResult::ReachedLoads);
|
|
return ReachedLoads[Definition];
|
|
}
|
|
|
|
template<class BBI, ReachingDefinitionsResult R>
|
|
const vector<Instruction *> &
|
|
ReachingDefinitionsImplPass<BBI, R>::getReachingDefinitions(LoadInst *Load) {
|
|
return ReachingDefinitions[Load];
|
|
}
|
|
|
|
template<class B, ReachingDefinitionsResult R>
|
|
unsigned
|
|
ReachingDefinitionsImplPass<B, R>::getReachingDefinitionsCount(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;
|
|
|
|
static RegisterPass<ReachingDefinitionsPass> X1("rdp",
|
|
"Reaching Definitions Pass",
|
|
true,
|
|
true);
|
|
|
|
static RegisterPass<ReachedLoadsPass> X2("rlp",
|
|
"Reaching Definitions Pass",
|
|
true,
|
|
true);
|
|
|
|
template<>
|
|
int32_t ReachingDefinitionsPass::getConditionIndex(TerminatorInst *V) {
|
|
return 0;
|
|
}
|
|
|
|
template<>
|
|
void ReachingDefinitionsPass::getAnalysisUsage(AnalysisUsage &AU) const {
|
|
AU.setPreservesAll();
|
|
}
|
|
|
|
template<>
|
|
int32_t ReachedLoadsPass::getConditionIndex(TerminatorInst *V) {
|
|
return 0;
|
|
}
|
|
|
|
template<>
|
|
void ReachedLoadsPass::getAnalysisUsage(AnalysisUsage &AU) const {
|
|
AU.setPreservesAll();
|
|
}
|
|
|
|
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);
|
|
|
|
// 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>();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
template<>
|
|
void
|
|
ConditionalReachedLoadsPass::getAnalysisUsage(AnalysisUsage &AU) const {
|
|
AU.setPreservesAll();
|
|
AU.addRequired<ConditionNumberingPass>();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
char ConditionNumberingPass::ID = 0;
|
|
static RegisterPass<ConditionNumberingPass> Z("cnp",
|
|
"Condition Numbering Pass",
|
|
true,
|
|
true);
|
|
|
|
bool ConditionNumberingPass::runOnFunction(Function &F) {
|
|
DBG("passes", { dbg << "Starting ConditionNumberingPass\n"; });
|
|
|
|
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;
|
|
for (auto &P : Conditions) {
|
|
if (P.second.size() > 1) {
|
|
// 0 is a reserved value
|
|
ConditionIndex++;
|
|
for (BranchInst *B : P.second)
|
|
BranchConditionNumberMap[B] = ConditionIndex;
|
|
|
|
DBG("cnp",
|
|
{
|
|
dbg << std::dec << ConditionIndex << ":";
|
|
for (BranchInst *B : P.second)
|
|
dbg << " " << getName(B);
|
|
dbg << "\n";
|
|
});
|
|
|
|
}
|
|
}
|
|
|
|
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) {
|
|
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
|
|
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::propagateTo(ConditionalBasicBlockInfo &Target,
|
|
TypeSizeProvider &TSP) {
|
|
bool Changed = false;
|
|
|
|
// Compute a bit vector with all the conditions that are incompatible with the
|
|
// target
|
|
llvm::BitVector Banned(SeenConditions.size());
|
|
|
|
// 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];
|
|
|
|
// 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) {
|
|
// Look for the BannedIndex in the current block's seen conditions
|
|
auto ConditionIt = std::find(SeenConditions.begin(),
|
|
SeenConditions.end(),
|
|
BannedIndex);
|
|
|
|
// If present set the corresponding bit in Banned
|
|
if (ConditionIt != SeenConditions.end())
|
|
Banned.set(ConditionIt - SeenConditions.begin());
|
|
}
|
|
|
|
}
|
|
|
|
for (auto &Definition : Definitions) {
|
|
// Check if this definition is compatible with the target basic block
|
|
llvm::BitVector DefinitionConditions = Definition.first;
|
|
DefinitionConditions &= Banned;
|
|
if (DefinitionConditions.any())
|
|
continue;
|
|
|
|
// Translate the conditions bitvector to the context of the target BBI
|
|
BitVector Translated(Target.SeenConditions.size());
|
|
|
|
for (int I = Definition.first.find_first();
|
|
I != -1;
|
|
I = Definition.first.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]);
|
|
|
|
// Keep the size of the new bitvector in sync
|
|
if (Target.SeenConditions.size() != Translated.size())
|
|
Translated.resize(Target.SeenConditions.size());
|
|
|
|
Translated.set(Index);
|
|
}
|
|
|
|
Changed |= Target.mergeDefinition({ Translated, Definition.second },
|
|
Target.Reaching,
|
|
TSP);
|
|
}
|
|
|
|
return Changed;
|
|
}
|
|
|
|
ConditionalBasicBlockInfo::ConditionsComparison
|
|
ConditionalBasicBlockInfo::mergeConditionBits(BitVector &Target,
|
|
BitVector &NewConditions) const {
|
|
// Find the different bits
|
|
BitVector DifferentBits = Target;
|
|
DifferentBits ^= NewConditions;
|
|
|
|
// If they are identical, quit
|
|
int FirstBit = DifferentBits.find_first();
|
|
if (FirstBit == -1)
|
|
return Identical;
|
|
|
|
// Ensure we only have two non-zero bits
|
|
int SecondBit = DifferentBits.find_next(FirstBit);
|
|
if (SecondBit == -1 || DifferentBits.find_next(SecondBit) != -1)
|
|
return Different;
|
|
|
|
// Check if the only two different bits are complementary conditions
|
|
if (SeenConditions[FirstBit] == -SeenConditions[SecondBit]) {
|
|
NewConditions.reset(FirstBit);
|
|
NewConditions.reset(SecondBit);
|
|
return Complementary;
|
|
} else {
|
|
return Different;
|
|
}
|
|
}
|
|
|
|
bool ConditionalBasicBlockInfo::mergeDefinition(CondDefPair NewDefinition,
|
|
vector<CondDefPair> &Targets,
|
|
TypeSizeProvider &TSP) const {
|
|
BitVector &NewConditionsBV = NewDefinition.first;
|
|
assert(NewConditionsBV.size() == SeenConditions.size());
|
|
|
|
bool Again = false;
|
|
bool Result = false;
|
|
do {
|
|
Again = false;
|
|
for (auto TargetIt = Targets.begin();
|
|
TargetIt != Targets.end();
|
|
TargetIt++) {
|
|
CondDefPair &Target = *TargetIt;
|
|
// Note that we copy the BitVector, since we're going to modify it
|
|
if (Target.second.I == NewDefinition.second.I) {
|
|
switch (mergeConditionBits(Target.first, NewConditionsBV)) {
|
|
case Identical:
|
|
return Result;
|
|
case Complementary:
|
|
Targets.erase(TargetIt);
|
|
Again = true;
|
|
Result = true;
|
|
break;
|
|
case Different:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} while (Again);
|
|
|
|
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());
|
|
|
|
bool Again = false;
|
|
bool Result = false;
|
|
llvm::SmallVector<BitVector, 2> &BVs = Targets[NewDefinition.second];
|
|
|
|
do {
|
|
Again = false;
|
|
for (auto TargetIt = BVs.begin(); TargetIt != BVs.end(); TargetIt++) {
|
|
switch (mergeConditionBits(*TargetIt, NewConditionsBV)) {
|
|
case Identical:
|
|
return Result;
|
|
case Complementary:
|
|
BVs.erase(TargetIt);
|
|
Again = true;
|
|
Result = true;
|
|
break;
|
|
case Different:
|
|
break;
|
|
}
|
|
}
|
|
} while (Again);
|
|
|
|
BVs.push_back(NewDefinition.first);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool isSupportedPointer(Value *V) {
|
|
if (auto *Global = dyn_cast<GlobalVariable>(V))
|
|
if (Global->getName() != "env")
|
|
return true;
|
|
|
|
if (isa<AllocaInst>(V))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
template<class BBI, ReachingDefinitionsResult R>
|
|
bool ReachingDefinitionsImplPass<BBI, R>::runOnFunction(Function &F) {
|
|
|
|
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();
|
|
|
|
auto &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
|
|
&& isSupportedPointer(Store->getPointerOperand())) {
|
|
|
|
// Record new definition
|
|
Info.newDefinition(Store, TSP);
|
|
|
|
} else if (Load != nullptr
|
|
&& isSupportedPointer(Load->getPointerOperand())) {
|
|
|
|
// 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;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
bool IsCall = false;
|
|
bool StorePCFound = false;
|
|
SmallVector<uint64_t, 3> ConstantStores;
|
|
auto It = BB->getTerminator()->getIterator();
|
|
while (It != BB->begin()) {
|
|
It--;
|
|
Instruction *I = &*It;
|
|
if (auto *Store = dyn_cast<StoreInst>(I)) {
|
|
Value *V = Store->getValueOperand();
|
|
if (Store->getPointerOperand()->getName() == "pc") {
|
|
StorePCFound = true;
|
|
} else if (auto *Constant = dyn_cast<ConstantInt>(V)) {
|
|
ConstantStores.push_back(Constant->getLimitedValue());
|
|
}
|
|
} else if (auto *Call = dyn_cast<CallInst>(I)) {
|
|
auto *Callee = Call->getCalledFunction();
|
|
if (Callee != nullptr && Callee->getName() == "newpc") {
|
|
uint64_t PC = getLimitedValue(Call->getArgOperand(0));
|
|
uint64_t Size = getLimitedValue(Call->getArgOperand(1));
|
|
auto RAIt = std::find(ConstantStores.begin(),
|
|
ConstantStores.end(),
|
|
PC + Size);
|
|
IsCall = StorePCFound && RAIt != ConstantStores.end();
|
|
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 (!IsCall && Size * SuccessorsCount <= 5000) {
|
|
// Get the identifier of the conditional instruction
|
|
int32_t ConditionIndex = getConditionIndex(BB->getTerminator());
|
|
|
|
// 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;
|
|
|
|
auto &SuccessorInfo = DefinitionsMap[Successor];
|
|
|
|
if (ConditionIndex != 0) {
|
|
SuccessorInfo.addCondition(ConditionIndex);
|
|
|
|
// If ConditionIndex is positive we're in the true branch, prepare
|
|
// ConditionIndex for the false branch
|
|
if (ConditionIndex > 0)
|
|
ConditionIndex = -ConditionIndex;
|
|
}
|
|
|
|
// Enqueue the successor only if the propagation actually did something
|
|
unsigned Old = SuccessorInfo.size();
|
|
if (Info.propagateTo(SuccessorInfo, TSP))
|
|
ToVisit.insert(Successor);
|
|
|
|
DBG("rdp-propagation",
|
|
dbg << getName(Successor)
|
|
<< " got " << (SuccessorInfo.size() - Old) << " new reachers "
|
|
<< "from " << getName(BB) << " (had " << Old << ")\n");
|
|
}
|
|
|
|
// 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
|
|
&& isSupportedPointer(Store->getPointerOperand())) {
|
|
|
|
// Remove all the reaching definitions aliased by this store
|
|
MemoryAccess TargetMA(Store, TSP);
|
|
erase_if(Definitions, [&TargetMA] (IMP &P) {
|
|
return TargetMA.mayAlias(P.second);
|
|
});
|
|
Definitions.push_back({ Store, TargetMA });
|
|
|
|
} else if (Load != nullptr
|
|
&& isSupportedPointer(Load->getPointerOperand())) {
|
|
|
|
// Record all the relevant reaching defininitions
|
|
MemoryAccess TargetMA(Load, TSP);
|
|
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;
|
|
});
|
|
|
|
} 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;
|
|
}
|