/// \file /// \brief Implementation of the ReachingDefinitionsPass // Standard includes #include #include #include #include #include #include #include // 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 const vector & ReachingDefinitionsImplPass::getReachedLoads(Instruction *Definition) { assert(R == ReachingDefinitionsResult::ReachedLoads); return ReachedLoads[Definition]; } template const vector & ReachingDefinitionsImplPass::getReachingDefinitions(LoadInst *Load) { return ReachingDefinitions[Load]; } template unsigned ReachingDefinitionsImplPass::getReachingDefinitionsCount(LoadInst *Load) { assert(R == ReachingDefinitionsResult::ReachedLoads); return ReachingDefinitionsCount[Load]; } using RDP = ReachingDefinitionsResult; template class ReachingDefinitionsImplPass; template class ReachingDefinitionsImplPass; template char ReachingDefinitionsImplPass::ID = 0; static RegisterPass X1("rdp", "Reaching Definitions Pass", true, true); static RegisterPass 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; template class ReachingDefinitionsImplPass; static RegisterPass Y1("crdp", "Conditional" " Reaching" " Definitions Pass", true, true); static RegisterPass Y2("crlp", "Conditional" " Reaching" " Definitions Pass", true, true); // TODO: this duplication sucks template<> int32_t ConditionalReachingDefinitionsPass::getConditionIndex(TerminatorInst *T) { auto *Branch = dyn_cast(T); if (Branch == nullptr || !Branch->isConditional()) return 0; return getAnalysis().getConditionIndex(T); } template<> void ConditionalReachingDefinitionsPass::getAnalysisUsage(AnalysisUsage &AU) const { AU.setPreservesAll(); AU.addRequired(); } template<> int32_t ConditionalReachedLoadsPass::getConditionIndex(TerminatorInst *T) { auto *Branch = dyn_cast(T); if (Branch == nullptr || !Branch->isConditional()) return 0; return getAnalysis().getConditionIndex(T); } template<> void ConditionalReachedLoadsPass::getAnalysisUsage(AnalysisUsage &AU) const { AU.setPreservesAll(); AU.addRequired(); } 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(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 WorkList; WorkList.push(V); while (!WorkList.empty()) { Value *V; V = WorkList.front(); WorkList.pop(); bool IsStore = isa(V); bool IsLoad = isa(V); if (IsStore || IsLoad) { // Load/store vs load/store if (IsStore) { Hash = combine(Hash, cast(V)->getPointerOperand()); } else { for (Instruction *I : RDP.getReachingDefinitions(cast(V))) { if (auto *Store = dyn_cast(I)) Hash = combine(Hash, Store->getPointerOperand()); else if (auto *Load = dyn_cast(I)) Hash = combine(Hash, Load->getPointerOperand()); } } } else if (auto *I = dyn_cast(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> 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(AV); bool AIsLoad = isa(AV); bool BIsStore = isa(BV); bool BIsLoad = isa(BV); if ((AIsStore || AIsLoad) && (BIsStore || BIsLoad)) { // Load/store vs load/store vector AStores; if (AIsStore) AStores.push_back(cast(AV)); else AStores = RDP.getReachingDefinitions(cast(AV)); vector BStores; if (BIsStore) BStores.push_back(cast(BV)); else BStores = RDP.getReachingDefinitions(cast(BV)); if (AStores != BStores) return false; } else if (auto *AI = dyn_cast(AV)) { // Instruction auto *BI = dyn_cast(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 Z("cnp", "Condition Numbering Pass", true, true); bool ConditionNumberingPass::runOnFunction(Function &F) { DBG("passes", { dbg << "Starting ConditionNumberingPass\n"; }); auto &RDP = getAnalysis(); unordered_map, ConditionHash, ConditionEqualTo> Conditions(10, ConditionHash(RDP), ConditionEqualTo(RDP)); // Group conditions together for (BasicBlock &BB : F) if (auto *Branch = dyn_cast(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 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(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> BasicBlockInfo::getReachingDefinitions(set &WhiteList, TypeSizeProvider &TSP) { vector> Result; for (const MemoryInstruction &MI : Reaching) { Instruction *I = MI.I; if (auto *Load = dyn_cast(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 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(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> ConditionalBasicBlockInfo::getReachingDefinitions(set &WhiteList, TypeSizeProvider &TSP) { vector> Result; for (auto &P : Reaching) { Instruction *I = P.first.I; if (auto *Load = dyn_cast(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(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 &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 &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(V)) if (Global->getName() != "env") return true; if (isa(V)) return true; return false; } template bool ReachingDefinitionsImplPass::runOnFunction(Function &F) { DBG("passes", { if (std::is_same::value) dbg << "Starting ConditionalReachingDefinitionsPass\n"; else dbg << "Starting ReachingDefinitionsPass\n"; }); for (auto &BB : F) { if (!BB.empty()) { if (auto *Call = dyn_cast(&*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 RPOT(&F); UniquedStack 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(&I); auto *Load = dyn_cast(&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 ConstantStores; auto It = BB->getTerminator()->getIterator(); while (It != BB->begin()) { It--; Instruction *I = &*It; if (auto *Store = dyn_cast(I)) { Value *V = Store->getValueOperand(); if (Store->getPointerOperand()->getName() == "pc") { StorePCFound = true; } else if (auto *Constant = dyn_cast(V)) { ConstantStores.push_back(Constant->getLimitedValue()); } } else if (auto *Call = dyn_cast(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 &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> Definitions; Definitions = Info.getReachingDefinitions(FreeLoads, TSP); for (Instruction &I : *BB) { auto *Store = dyn_cast(&I); auto *Load = dyn_cast(&I); using IMP = pair; 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(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 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::value) dbg << "Ending ConditionalReachingDefinitionsPass\n"; else dbg << "Ending ReachingDefinitionsPass\n"; }); return false; }