diff --git a/CMakeLists.txt b/CMakeLists.txt index ab3138e24..76e31903b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,7 @@ include_directories(argparse/) add_executable(revamb ptcdump.cpp main.cpp debughelper.cpp variablemanager.cpp jumptargetmanager.cpp instructiontranslator.cpp codegenerator.cpp - debug.cpp argparse/argparse.c) + debug.cpp osra.cpp argparse/argparse.c) target_link_libraries(revamb dl m ${LLVM_LIBRARIES}) include(tests/Tests.cmake) diff --git a/codegenerator.cpp b/codegenerator.cpp index 39000d127..db463305e 100644 --- a/codegenerator.cpp +++ b/codegenerator.cpp @@ -58,12 +58,14 @@ CodeGenerator::CodeGenerator(std::string Input, DebugInfoType DebugInfo, std::string Debug, std::string LinkingInfoPath, - std::string CoveragePath) : + std::string CoveragePath, + bool EnableOSRA) : TargetArchitecture(Target), Context(getGlobalContext()), TheModule((new Module("top", Context))), OutputPath(Output), - Debug(new DebugHelper(Output, Debug, TheModule.get(), DebugInfo)) + Debug(new DebugHelper(Output, Debug, TheModule.get(), DebugInfo)), + EnableOSRA(EnableOSRA) { OriginalInstrMDKind = Context.getMDKindID("oi"); PTCInstrMDKind = Context.getMDKindID("pi"); @@ -644,7 +646,8 @@ void CodeGenerator::translate(uint64_t VirtualAddress, JumpTargetManager JumpTargets(MainFunction, PCReg, SourceArchitecture, - Segments); + Segments, + EnableOSRA); if (VirtualAddress == 0) { JumpTargets.harvestGlobalData(); @@ -795,7 +798,7 @@ void CodeGenerator::translate(uint64_t VirtualAddress, } // End loop over instructions if (ForceNewBlock) - JumpTargets.getBlockAt(EndPC); + JumpTargets.getBlockAt(EndPC, false); // We might have a leftover block, probably due to the block created after // the last call to exit_tb diff --git a/codegenerator.h b/codegenerator.h index ae8557ae7..1ba615f05 100644 --- a/codegenerator.h +++ b/codegenerator.h @@ -50,7 +50,8 @@ public: DebugInfoType DebugInfo, std::string Debug, std::string LinkingInfoPath, - std::string CoveragePath); + std::string CoveragePath, + bool EnableOSRA); ~CodeGenerator(); @@ -88,6 +89,7 @@ private: unsigned DbgMDKind; std::string CoveragePath; + bool EnableOSRA; }; #endif // _CODEGENERATOR_H diff --git a/instructiontranslator.cpp b/instructiontranslator.cpp index 08ab2f4de..e499db6da 100644 --- a/instructiontranslator.cpp +++ b/instructiontranslator.cpp @@ -512,6 +512,7 @@ void InstructionTranslator::removeNewPCMarkers(std::string &CoveragePath) { Output << "0x" << PC << ",0x" << Size << "," << (JumpTargets.isJumpTarget(PC) ? "1" : "0") + << "," << (JumpTargets.isReliablePC(PC) ? "1" : "0") << std::endl; } } @@ -732,7 +733,7 @@ bool InstructionTranslator::translate(PTCInstruction *Instr, if (Constant != nullptr) { uint64_t Address = Constant->getLimitedValue(); if (PC != Address) - JumpTargets.getBlockAt(Address); + JumpTargets.getBlockAt(Address, PC != NextPC); } } } diff --git a/jumptargetmanager.cpp b/jumptargetmanager.cpp index 0a21bd18a..c9243d178 100644 --- a/jumptargetmanager.cpp +++ b/jumptargetmanager.cpp @@ -30,6 +30,7 @@ #include "debug.h" #include "revamb.h" #include "ir-helpers.h" +#include "osra.h" #include "jumptargetmanager.h" using namespace llvm; @@ -68,13 +69,16 @@ bool TranslateDirectBranchesPass::runOnFunction(Function &F) { // Is destination a constant? if (PCWrite != nullptr) { - if (isSumJump(PCWrite)) - JTM->getBlockAt(getNextPC(PCWrite)); + uint64_t NextPC = JTM->getNextPC(PCWrite); + if (NextPC != 0 && JTM->isOSRAEnabled() && isSumJump(PCWrite)) + JTM->getBlockAt(NextPC, false); - if (auto *Address = dyn_cast(PCWrite->getValueOperand())) { + auto *Address = dyn_cast(PCWrite->getValueOperand()); + if (Address != nullptr) { // Compute the actual PC and get the associated BasicBlock uint64_t TargetPC = Address->getSExtValue(); - BasicBlock *TargetBlock = JTM->getBlockAt(TargetPC); + bool IsReliable = NextPC != 0 && TargetPC != NextPC; + BasicBlock *TargetBlock = JTM->getBlockAt(TargetPC, IsReliable); // Remove unreachable right after the exit_tb BasicBlock::iterator CallIt(Call); @@ -148,49 +152,474 @@ uint64_t TranslateDirectBranchesPass::getNextPC(Instruction *TheInstruction) { char JumpTargetsFromConstantsPass::ID = 0; -bool JumpTargetsFromConstantsPass::runOnFunction(Function &F) { - for (BasicBlock& BB : make_range(F.begin(), F.end())) - if (Visited->find(&BB) == Visited->end()) { - Visited->insert(&BB); +void JumpTargetsFromConstantsPass::getAnalysisUsage(AnalysisUsage &AU) const { + if (UseOSRA) + AU.addRequired(); +} - std::stack WorkList; +void JumpTargetsFromConstantsPass::enqueueStores(LoadInst *Start, + unsigned StackHeight, + std::vector>& WL) { + auto *Destination = Start->getPointerOperand(); + std::stack> ToExplore; + std::set Visited; + ToExplore.push(std::make_pair(Start, 0)); - // Use a lambda so we don't have to initialize the queue with all the - // instructions - auto Process = [this, &WorkList] (User *U) { - auto *Call = dyn_cast(U); - // TODO: comparing strings is not very elegant - if (Call != nullptr && Call->getCalledFunction()->getName() == "newpc") - return; + Instruction *I = Start; - auto *Store = dyn_cast(U); - if (Store != nullptr && JTM->isPCReg(Store->getPointerOperand())) - return; + while (!ToExplore.empty()) { + unsigned Depth; + std::tie(I, Depth) = ToExplore.top(); + ToExplore.pop(); - for (Use& Operand : U->operands()) { - auto *OperandUser = dyn_cast(Operand.get()); - if (OperandUser != nullptr - && OperandUser->op_begin() != OperandUser->op_end()) { - WorkList.push(OperandUser); - } + auto *BB = I->getParent(); + if (Visited.find(BB) != Visited.end()) + continue; - auto *Constant = dyn_cast(Operand.get()); - if (Constant != nullptr) - JTM->getBlockAt(Constant->getLimitedValue()); + Visited.insert(BB); + BasicBlock::reverse_iterator It(make_reverse_iterator(I)); + BasicBlock::reverse_iterator Begin(BB->rend()); + bool Found = false; + for (; It != Begin; It++) { + if (auto *Store = dyn_cast(&*It)) { + if (Store->getPointerOperand() == Destination) { + auto NewPair = std::make_pair(Store->getValueOperand(), StackHeight); + if (std::find(WL.begin(), WL.end(), NewPair) == WL.end()) + WL.push_back(NewPair); + Found = true; + break; } - - }; - - for (Instruction& Instr : BB) - Process(&Instr); - - while (!WorkList.empty()) { - auto *Current = WorkList.top(); - WorkList.pop(); - Process(Current); } } + + // If we haven't find a store, proceed recursively in the predecessors + if (!Found && Depth < MaxDepth) { + auto Predecessors = make_range(pred_begin(BB), pred_end(BB)); + for (BasicBlock *Predecessor : Predecessors) { + if (Predecessor != JTM->dispatcher() + && !Predecessor->empty()) { + ToExplore.push(std::make_pair(&*Predecessor->rbegin(), + Depth + 1)); + } + } + } + + } + +} + +Constant *JumpTargetManager::readConstantPointer(Constant *Address, + Type *PointerTy) { + auto *Value = readConstantInt(Address, SourceArchitecture.pointerSize()); + if (Value != nullptr) { + return ConstantExpr::getIntToPtr(Value, PointerTy); + } else { + return nullptr; + } +} + +ConstantInt *JumpTargetManager::readConstantInt(Constant *ConstantAddress, + unsigned Size) { + const DataLayout &DL = TheModule.getDataLayout(); + + if (ConstantAddress->getType()->isPointerTy()) { + using CE = ConstantExpr; + auto IntPtrTy = Type::getIntNTy(Context, SourceArchitecture.pointerSize()); + ConstantAddress = CE::getPtrToInt(ConstantAddress, IntPtrTy); + } + + uint64_t Address = getZExtValue(ConstantAddress, DL); + + for (auto &Segment : Segments) { + // Note: we also consider writeable memory areas because, despite being + // modifiable, can contain useful information + if (Segment.StartVirtualAddress <= Address + && Address < Segment.EndVirtualAddress + && Segment.IsReadable) { + auto *Array = cast(Segment.Variable->getInitializer()); + StringRef RawData = Array->getRawDataValues(); + const unsigned char *RawDataPtr = RawData.bytes_begin(); + uint64_t Offset = Address - Segment.StartVirtualAddress; + const unsigned char *Start = RawDataPtr + Offset; + + using support::endian::read; + using support::endianness; + uint64_t Value; + switch (Size) { + case 1: + Value = read(Start); + break; + case 2: + if (DL.isLittleEndian()) + Value = read(Start); + else + Value = read(Start); + break; + case 4: + if (DL.isLittleEndian()) + Value = read(Start); + else + Value = read(Start); + break; + case 8: + if (DL.isLittleEndian()) + Value = read(Start); + else + Value = read(Start); + break; + default: + assert(false); + } + + return ConstantInt::get(IntegerType::get(Context, Size * 8), Value); + } + } + + return nullptr; +} + +class OperationsStack { +public: + OperationsStack(JumpTargetManager *JTM, + const DataLayout &DL) : JTM(JTM), DL(DL) { } + + void explore(Constant *NewOperand); + uint64_t materialize(Constant *NewOperand); + + void reset(bool Reliable) { + Operations.clear(); + OperationsSet.clear(); + IsReliable = Reliable; + } + + void registerPCs() const { + for (auto Pair : PCs) + JTM->getBlockAt(Pair.first, Pair.second); + } + + void cut(unsigned Height) { + assert(Height <= Operations.size()); + while (Height != Operations.size()) { + Instruction *Op = Operations.back(); + auto It = OperationsSet.find(Op); + if (It != OperationsSet.end()) + OperationsSet.erase(It); + else if (isa(Op)) { + // It's not in OperationsSet, it might a binary instruction where we + // forced one operand to be constant, or an instruction generated from a + // constant unary expression + unsigned FreeOpIndex = isa(Op->getOperand(0)) ? 1 : 0; + auto *FreeOp = cast(Op->getOperand(FreeOpIndex)); + auto It = OperationsSet.find(FreeOp); + assert(It != OperationsSet.end()); + OperationsSet.erase(It); + } + Operations.pop_back(); + } + } + + bool insertIfNew(Instruction *I) { + if (OperationsSet.find(I) == OperationsSet.end()) { + Operations.push_back(I); + OperationsSet.insert(I); + return true; + } + return false; + } + + bool insertIfNew(Instruction *I, Instruction *Ref) { + if (OperationsSet.find(Ref) == OperationsSet.end()) { + Operations.push_back(I); + OperationsSet.insert(Ref); + return true; + } + return false; + } + + void insert(Instruction *I) { + Operations.push_back(I); + } + + unsigned height() const { return Operations.size(); } + +private: + JumpTargetManager *JTM; + const DataLayout &DL; + + std::vector Operations; + std::set OperationsSet; + std::set> PCs; + + bool IsReliable; +}; + +uint64_t OperationsStack::materialize(Constant *NewOperand) { + for (Instruction *I : make_range(Operations.rbegin(), Operations.rend())) { + if (auto *Load = dyn_cast(I)) { + // OK, we've got a load, let's see if the load address is + // constant + assert(NewOperand != nullptr && !isa(NewOperand)); + + if (Load->getType()->isIntegerTy()) { + unsigned Size = Load->getType()->getPrimitiveSizeInBits() / 8; + assert(Size != 0); + NewOperand = JTM->readConstantInt(NewOperand, Size); + } else if (Load->getType()->isPointerTy()) { + NewOperand = JTM->readConstantPointer(NewOperand, + Load->getType()); + } else { + assert(false); + } + + if (NewOperand == nullptr) + break; + } else if (auto *Call = dyn_cast(I)) { + Function *Callee = Call->getCalledFunction(); + assert(Callee != nullptr + && Callee->getIntrinsicID() == Intrinsic::bswap); + uint64_t Value = NewOperand->getUniqueInteger().getLimitedValue(); + + Type *T = NewOperand->getType(); + if (T->isIntegerTy(16)) + Value = ByteSwap_16(Value); + else if (T->isIntegerTy(32)) + Value = ByteSwap_32(Value); + else if (T->isIntegerTy(64)) + Value = ByteSwap_64(Value); + + NewOperand = ConstantInt::get(T, Value); + } else { + // Replace non-const operand with NewOperand + std::vector Operands; + bool NonConstFound = false; + for (Value *Op : I->operand_values()) { + if (auto *Const = dyn_cast(Op)) { + Operands.push_back(Const); + } else { + assert(!NonConstFound); + NonConstFound = true; + Operands.push_back(NewOperand); + } + } + + NewOperand = ConstantFoldInstOperands(I->getOpcode(), + I->getType(), + Operands, + DL); + assert(NewOperand != nullptr); + } + } + + // We made it, mark the value to be explored + if (NewOperand != nullptr) { + assert(!isa(NewOperand)); + return getZExtValue(NewOperand, DL); + } + + return 0; +} + +void OperationsStack::explore(Constant *NewOperand) { + uint64_t PC = materialize(NewOperand); + + if (PC != 0 && JTM->isInterestingPC(PC)) + PCs.insert({ PC, IsReliable }); +} + +bool JumpTargetsFromConstantsPass::runOnFunction(Function &F) { + OSRAPass *OSRA = getAnalysisIfAvailable(); + const DataLayout &DL = F.getParent()->getDataLayout(); + OperationsStack OS(JTM, DL); + + for (BasicBlock& BB : make_range(F.begin(), F.end())) { + + if (Visited->find(&BB) != Visited->end()) + continue; + Visited->insert(&BB); + + for (Instruction& Instr : BB) { + assert(Instr.getParent() == &BB); + + auto *Store = dyn_cast(&Instr); + auto *Load = dyn_cast(&Instr); + bool IsStore = Store != nullptr; + bool IsPCStore = IsStore && JTM->isPCReg(Store->getPointerOperand()); + + // Keep this for future use + bool IsLoad = false && Load != nullptr; + if ((!IsStore && !IsLoad) + || (IsPCStore + && isa(Store->getValueOperand())) + || (IsLoad + && (isa(Load->getPointerOperand()) + || isa(Load->getPointerOperand())))) + continue; + + // Operations is a stack of ConstantInt uses in a BinaryOperator + // TODO: hardcoded + OS.reset(/* IsPCStore */ false); + std::vector> WorkList; + if (IsStore) + WorkList.push_back(std::make_pair(Store->getValueOperand(), 0)); + else + WorkList.push_back(std::make_pair(Load->getPointerOperand(), 0)); + + std::set Visited; + + while (!WorkList.empty()) { + unsigned Height; + Value *V; + std::tie(V, Height) = WorkList.back(); + WorkList.pop_back(); + Value *Next = V; + + if (Visited.find(V) != Visited.end()) + continue; + Visited.insert(V); + + // Discard operations we no longer need + OS.cut(Height); + + while (Next != nullptr) { + V = Next; + Next = nullptr; + + if (auto *C = dyn_cast(V)) { + // We reached the end of the path, materialize the value + OS.explore(C); + } else if (auto *BinOp = dyn_cast(V)) { + + // Append a reference to the operation to the Operations stack + Use& FirstOp = BinOp->getOperandUse(0); + Use& SecondOp = BinOp->getOperandUse(1); + + if (isa(FirstOp.get()) + || isa(SecondOp.get())) { + assert(!(isa(FirstOp.get()) + && isa(SecondOp.get()))); + bool FirstConstant = isa(FirstOp.get()); + + // Add to the operations stack the constant one and proceed with + // the other + if (OS.insertIfNew(BinOp)) + Next = FirstConstant ? SecondOp.get() : FirstOp.get(); + } else if (OSRA != nullptr) { + Constant *ConstantOp = nullptr; + Value *FreeOp = nullptr; + Type *Int64 = Type::getInt64Ty(F.getParent()->getContext()); + std::tie(ConstantOp, FreeOp) = OSRA->identifyOperands(BinOp, + Int64, + DL); + + if (FreeOp == nullptr && ConstantOp != nullptr) { + // The operation has been folded + OS.explore(ConstantOp); + } else if (FreeOp != nullptr && ConstantOp != nullptr) { + // We were able to identify a constant operand + unsigned FreeOpIndex = BinOp->getOperand(0) == FreeOp ? 0 : 1; + + Instruction *Clone = BinOp->clone(); + Clone->setOperand(1 - FreeOpIndex, ConstantOp); + // This is a dirty trick to keep track of the original + // instruction + Clone->setOperand(FreeOpIndex, BinOp); + // TODO: this might leave to infinte loops + if (OS.insertIfNew(Clone, BinOp)) + Next = BinOp->getOperandUse(FreeOpIndex).get(); + } + } + } else if (auto *Load = dyn_cast(V)) { + auto *Pointer = Load->getPointerOperand(); + + // If we're loading a global or local variable, look for the last + // write to that variable, otherwise see if it's a load from a + // constant address which points to a constant memory area + if (isa(Pointer) || isa(Pointer)) { + enqueueStores(Load, OS.height(), WorkList); + } else { + if (OS.insertIfNew(Load)) + Next = Pointer; + } + } else if (auto *Unary = dyn_cast(V)) { + if (OS.insertIfNew(Unary)) + Next = Unary->getOperand(0); + } else if (auto *Expression = dyn_cast(V)) { + if (Expression->getNumOperands() == 1) { + auto *ExprAsInstr = Expression->getAsInstruction(); + OS.insert(ExprAsInstr); + Next = Expression->getOperand(0); + } + } else if (auto *Call = dyn_cast(V)) { + Function *Callee = Call->getCalledFunction(); + if (Callee != nullptr + && Callee->getIntrinsicID() == Intrinsic::bswap) { + OS.insert(Call); + Next = Call->getArgOperand(0); + } + } // End of the switch over instruction type + + // We don't know how to proceed, but we can still check if the + // current instruction is associated with a suitable OSR. + if (OSRA != nullptr && Next == nullptr && OS.height() > 0) { + const OSRAPass::OSR *O = OSRA->getOSR(V); + if (O == nullptr) + continue; + + using CI = ConstantInt; + Type *Int64 = IntegerType::get(F.getParent()->getContext(), 64); + if (O->isConstant()) { + // If it's just a single constant, use it + OS.explore(CI::get(Int64, O->base())); + } else if (!O->boundedValue()->isTop() + && !O->boundedValue()->isBottom() + && O->boundedValue()->isSingleRange()) { + // We have a limited range, let's use it all + + // Perform a preliminary check that whole range fits into the + // executable area + Constant *MinConst, *MaxConst; + std::tie(MinConst, MaxConst) = O->boundaries(Int64, DL); + uint64_t Min = getZExtValue(MinConst, DL); + uint64_t Max = getZExtValue(MaxConst, DL); + // uint64_t Step = O->absFactor(Int64, DL); + uint64_t Step = O->factor(); + + // TODO: the O->size() threshold is pretty arbitrary, the best + // solution here is probably restore it to int64_t::max(), + // assert if it's larger than 10000 and only apply it to + // store to memory, pc and maybe other registers (lr?) + auto MaterializedMin = OS.materialize(MinConst); + auto MaterializedMax = OS.materialize(MaxConst); + auto MaterializedStep = OS.materialize(CI::get(Int64, Step)); + if (!JTM->isExecutableRange(MaterializedMin, MaterializedMax) + || !JTM->isInstructionAligned(MaterializedStep) + || O->size() >= 10000) + continue; + + if (O->size() > 1000) + dbg << "Warning: " << O->size() << " jump targets added\n"; + + DBG("osrjts", dbg << "Adding " << std::dec << O->size() + << " jump targets from 0x" + << std::hex << JTM->getPC(&Instr).first << "\n"); + + // Note: addition and comparison for equality are all sign-safe + // operations, no need to use Constants in this case. + // TODO: switch to a super-elegant iterator + for (uint64_t Position = Min; Position != Max; Position += Step) + OS.explore(CI::get(Int64, Position)); + OS.explore(CI::get(Int64, Max)); + } + } + + } + } + } + } + + OS.registerPCs(); + return false; } @@ -203,7 +632,8 @@ static cl::opt *getOption(StringMap& Options, JumpTargetManager::JumpTargetManager(Function *TheFunction, Value *PCReg, Architecture& SourceArchitecture, - std::vector& Segments) : + std::vector& Segments, + bool EnableOSRA) : TheModule(*TheFunction->getParent()), Context(TheModule.getContext()), TheFunction(TheFunction), @@ -214,7 +644,8 @@ JumpTargetManager::JumpTargetManager(Function *TheFunction, Dispatcher(nullptr), DispatcherSwitch(nullptr), Segments(Segments), - SourceArchitecture(SourceArchitecture) { + SourceArchitecture(SourceArchitecture), + EnableOSRA(EnableOSRA) { FunctionType *ExitTBTy = FunctionType::get(Type::getVoidTy(Context), { }, false); @@ -265,8 +696,10 @@ void JumpTargetManager::findCodePointers(const unsigned char *Start, using support::endian::read; using support::endianness; for (; Start < End - sizeof(value_type); Start++) { - uint64_t Value = read(endian), 1>(Start); - getBlockAt(Value); + uint64_t Value = read(endian), + 1>(Start); + getBlockAt(Value, false); } } @@ -313,7 +746,7 @@ BasicBlock *JumpTargetManager::newPC(uint64_t PC, bool& ShouldContinue) { auto OIAIt = OriginalInstructionAddresses.find(PC); if (OIAIt != OriginalInstructionAddresses.end()) { ShouldContinue = false; - return getBlockAt(PC); + return getBlockAt(PC, false); } // We don't know anything about this PC @@ -447,11 +880,15 @@ static bool isSumJump(StoreInst *PCWrite) { return false; } -uint64_t JumpTargetManager::getNextPC(Instruction *TheInstruction) { +std::pair +JumpTargetManager::getPC(Instruction *TheInstruction) const { CallInst *NewPCCall = nullptr; std::set Visited; std::queue WorkList; - WorkList.push(make_reverse_iterator(TheInstruction)); + if (TheInstruction->getIterator() == TheInstruction->getParent()->begin()) + WorkList.push(--TheInstruction->getParent()->rend()); + else + WorkList.push(make_reverse_iterator(TheInstruction)); while (!WorkList.empty()) { auto I = WorkList.front(); @@ -465,7 +902,11 @@ uint64_t JumpTargetManager::getNextPC(Instruction *TheInstruction) { if (auto Marker = dyn_cast(&*I)) { // TODO: comparing strings is not very elegant if (Marker->getCalledFunction()->getName() == "newpc") { - assert(NewPCCall == nullptr && "Two candidates calls to newpc found"); + + // We found two distinct newpc leading to the requested instruction + if (NewPCCall != nullptr) + return { 0, 0 }; + NewPCCall = Marker; break; } @@ -495,18 +936,21 @@ uint64_t JumpTargetManager::getNextPC(Instruction *TheInstruction) { } - assert(NewPCCall != nullptr && "Couldn't find the current PC"); + // Couldn't find the current PC + if (NewPCCall == nullptr) + return { 0, 0 }; uint64_t PC = getConst(NewPCCall->getArgOperand(0)); uint64_t Size = getConst(NewPCCall->getArgOperand(1)); assert(Size != 0); - return PC + Size; + return { PC, Size }; } void JumpTargetManager::handleSumJump(Instruction *SumJump) { // Take the next PC uint64_t NextPC = getNextPC(SumJump); - BasicBlock *BB = getBlockAt(NextPC); + assert(NextPC != 0); + BasicBlock *BB = getBlockAt(NextPC, false); assert(BB && !BB->empty()); std::set Visited; @@ -527,18 +971,23 @@ void JumpTargetManager::handleSumJump(Instruction *SumJump) { // TODO: comparing strings is not very elegant if (Callee != nullptr && Callee->getName() == "newpc") { uint64_t PC = getConst(Call->getArgOperand(0)); - if (PC == NextPC) { - // Split and update iterators to proceed - BB = getBlockAt(PC); - I = BB->begin(); - End = BB->end(); - // Updated the expectation for the next PC - NextPC = PC + getConst(Call->getArgOperand(1)); - } else { - // We've found a (direct or indirect) jump, stop + // If we've found a (direct or indirect) jump, stop + if (PC != NextPC) return; - } + + // Split and update iterators to proceed + BB = getBlockAt(PC, false); + + // Do we have a block? + if (BB == nullptr) + return; + + I = BB->begin(); + End = BB->end(); + + // Updated the expectation for the next PC + NextPC = PC + getConst(Call->getArgOperand(1)); } else if (Call->getCalledFunction() == ExitTB) { // We've found an unparsed indirect jump return; @@ -574,7 +1023,7 @@ void JumpTargetManager::translateIndirectJumps() { || !isa(PCWrite->getValueOperand())) && "Direct jumps should not be handled here"); - if (PCWrite != nullptr && isSumJump(PCWrite)) + if (PCWrite != nullptr && EnableOSRA && isSumJump(PCWrite)) handleSumJump(PCWrite); BasicBlock *BB = Call->getParent(); @@ -639,11 +1088,13 @@ void JumpTargetManager::unvisit(BasicBlock *BB) { } /// Get or create a block for the given PC -BasicBlock *JumpTargetManager::getBlockAt(uint64_t PC) { - if (!isExecutableAddress(PC)) { - assert("Jump to a non-executable address"); +BasicBlock *JumpTargetManager::getBlockAt(uint64_t PC, bool Reliable) { + if (!isExecutableAddress(PC) + || !isInstructionAligned(PC)) return nullptr; - } + + if (Reliable) + ReliablePCs.insert(PC); // Do we already have a BasicBlock for this PC? BlockMap::iterator TargetIt = JumpTargets.find(PC); @@ -726,24 +1177,6 @@ void JumpTargetManager::createDispatcher(Function *OutputFunction, } void JumpTargetManager::harvest() { - // First attempt: run SROA and look for new direct branch targets - if (empty()) { - DBG("verify", if (verifyModule(TheModule, &dbgs())) { abort(); }); - - DBG("jtcount", dbg - << "We're out of targets. Trying with SROA and" - << " TranslateDirectBranchesPass\n"); - - legacy::PassManager PM; - PM.add(createSROAPass()); - PM.add(new TranslateDirectBranchesPass(this)); - PM.run(TheModule); - DBG("jtcount", dbg - << "JumpTargets found: " << Unexplored.size() << "\n"); - } - - // Second attempt: run EarlyCSE and collect candidate code pointers from - // constants in the code and look for new direct jumps if (empty()) { DBG("verify", if (verifyModule(TheModule, &dbgs())) { abort(); }); @@ -751,75 +1184,31 @@ void JumpTargetManager::harvest() { << "Trying with EarlyCSE and JumpTargetsFromConstantsPass\n"); legacy::PassManager PM; + PM.add(createSROAPass()); // temp + PM.add(createConstantPropagationPass()); // temp PM.add(createEarlyCSEPass()); - Visited.clear(); - PM.add(new JumpTargetsFromConstantsPass(this, &Visited)); + PM.add(new JumpTargetsFromConstantsPass(this, false, &Visited)); PM.add(new TranslateDirectBranchesPass(this)); PM.run(TheModule); DBG("jtcount", dbg << "JumpTargets found: " << Unexplored.size() << "\n"); } - // Third attempt: - // - // * clone the whole translated function - // * remove calls to newpc to allow optimizations to be more aggressive - // * run EarlyCSE - // * collect aliasing information - // * run GVN using aliasing information - // * collect candidate code pointers from the new function - // * discarded the cloned function - // - // TODO: there's *huge* space for improvement here, for instance we could - // avoid to clone the function by implementing a proper data-flow - // analysis propagating constant data without actually changing the - // code. Also, running GVN on the whole code doesn't make much sense. - if (empty()) { + if (EnableOSRA && empty()) { + DBG("verify", if (verifyModule(TheModule, &dbgs())) { abort(); }); + DBG("jtcount", dbg - << "Trying to remove calls to newpc, EarlyCSE and GVN\n"); + << "Trying with EarlyCSE and JumpTargetsFromConstantsPass\n"); - // Prepare cloned function - Function *ClonedFunction = Function::Create(TheFunction->getFunctionType(), - TheFunction->getLinkage(), - "", - &TheModule); - - // Clone function body - ValueToValueMapTy Ignore1; - SmallVector Ignore2; - CloneFunctionInto(ClonedFunction, TheFunction, Ignore1, false, Ignore2); - - // Remove all the PC markers - auto *NewPC = TheModule.getFunction("newpc"); - auto It = NewPC->user_begin(); - auto End = NewPC->user_end(); - while (It != End) { - auto *CallInstruction = cast(*It++); - if (CallInstruction->getParent()->getParent() == ClonedFunction) - CallInstruction->eraseFromParent(); - } - - // Force the cloned function to start from the dispatcher, so we can be sure - // that all the code will be considered and optimized - auto FirstBB = ClonedFunction->begin(); - assert(FirstBB != ClonedFunction->end()); - auto LastInstructionIt = FirstBB->rbegin(); - assert(LastInstructionIt != FirstBB->rend()); - auto *LastInstruction = cast(&*LastInstructionIt); - LastInstruction->swapSuccessors(); - - // Run the various optimization steps - legacy::PassManager PM; - PM.add(createEarlyCSEPass()); - PM.add(createScopedNoAliasAAWrapperPass()); - PM.add(createGVNPass(false)); Visited.clear(); - PM.add(new JumpTargetsFromConstantsPass(this, &Visited)); + + legacy::PassManager PM; + PM.add(createSROAPass()); // temp + PM.add(createConstantPropagationPass()); // temp + PM.add(createEarlyCSEPass()); + PM.add(new JumpTargetsFromConstantsPass(this, true, &Visited)); + PM.add(new TranslateDirectBranchesPass(this)); PM.run(TheModule); - - // Remove the cloned function - ClonedFunction->eraseFromParent(); - DBG("jtcount", dbg << "JumpTargets found: " << Unexplored.size() << "\n"); } diff --git a/jumptargetmanager.h b/jumptargetmanager.h index c8d83f829..a92e24b9a 100644 --- a/jumptargetmanager.h +++ b/jumptargetmanager.h @@ -5,6 +5,7 @@ #include #include #include +#include // Forward declarations namespace llvm { @@ -12,8 +13,10 @@ class BasicBlock; class Function; class Instruction; class LLVMContext; +class LoadInst; class Module; class SwitchInst; +class StoreInst; class Value; } @@ -56,19 +59,32 @@ public: JumpTargetsFromConstantsPass() : llvm::FunctionPass(ID), JTM(nullptr), - Visited(nullptr) { } + Visited(nullptr), + UseOSRA(false) { } JumpTargetsFromConstantsPass(JumpTargetManager *JTM, + bool UseOSRA, std::set *Visited) : llvm::FunctionPass(ID), JTM(JTM), - Visited(Visited) { } + Visited(Visited), + UseOSRA(UseOSRA) { } bool runOnFunction(llvm::Function &F) override; + void getAnalysisUsage(llvm::AnalysisUsage &AU) const; + private: + void enqueueStores(llvm::LoadInst *Start, + unsigned StackHeight, + std::vector>& WL); + + +private: + const unsigned MaxDepth = 3; JumpTargetManager *JTM; std::set *Visited; + bool UseOSRA; }; class JumpTargetManager { @@ -81,7 +97,8 @@ public: JumpTargetManager(llvm::Function *TheFunction, llvm::Value *PCReg, Architecture& SourceArchitecture, - std::vector& Segments); + std::vector& Segments, + bool EnableOSRA); void harvestGlobalData(); @@ -117,6 +134,8 @@ public: llvm::Function *exitTB() { return ExitTB; } + bool isOSRAEnabled() { return EnableOSRA; } + /// Pop from the list of program counters to explore /// /// \return a pair containing the PC and the initial block to use, or @@ -126,18 +145,77 @@ public: /// Return true if there are unexplored jump targets bool empty() { return Unexplored.empty(); } + bool isExecutableRange(uint64_t Start, uint64_t End) const { + for (std::pair Range : ExecutableRanges) + if (Range.first <= Start && Start < Range.second + && Range.first <= End && End < Range.second) + return true; + return false; + } + + bool isInstructionAligned(uint64_t PC) const { + return PC % SourceArchitecture.instructionAlignment() == 0; + } + + bool isInterestingPC(uint64_t PC) const { + return isExecutableAddress(PC) + && isInstructionAligned(PC) + && JumpTargets.find(PC) == JumpTargets.end(); + } + + bool isExecutableAddress(uint64_t Address) const { + for (std::pair Range : ExecutableRanges) + if (Range.first <= Address && Address < Range.second) + return true; + return false; + } + bool isJumpTarget(uint64_t PC) { return JumpTargets.count(PC); } + bool isReliablePC(uint64_t PC) { + // Get the PC of the basic block "not less than" the PC + auto It = JumpTargets.lower_bound(PC); + + uint64_t BBPC = 0; + if (It == JumpTargets.end()) { + BBPC = JumpTargets.rbegin()->first; + assert(BBPC < PC); + } else { + + BBPC = It->first; + + // If it's not the PC itself, it's the PC of the next basic + // block, so go back one position + if (BBPC != PC) { + assert(It != JumpTargets.begin()); + BBPC = (--It)->first; + } + } + + return ReliablePCs.count(BBPC); + } + /// Get or create a block for the given PC - llvm::BasicBlock *getBlockAt(uint64_t PC); + llvm::BasicBlock *getBlockAt(uint64_t PC, bool Reliable); - llvm::BasicBlock *dispatcher() { return Dispatcher; } + void unvisit(llvm::BasicBlock *BB); - bool isPCReg(llvm::Value *TheValue) { return TheValue == PCReg; } + llvm::BasicBlock *dispatcher() const { return Dispatcher; } - uint64_t getNextPC(llvm::Instruction *TheInstruction); + bool isPCReg(llvm::Value *TheValue) const { return TheValue == PCReg; } + llvm::Value *pcReg() const { return PCReg; } + + std::pair getPC(llvm::Instruction *TheInstruction) const; + uint64_t getNextPC(llvm::Instruction *TheInstruction) const { + auto Pair = getPC(TheInstruction); + return Pair.first + Pair.second; + } + + + llvm::ConstantInt *readConstantInt(llvm::Constant *Address, unsigned Size); + llvm::Constant *readConstantPointer(llvm::Constant *Address, llvm::Type *PointerTy); private: // TODO: instead of a gigantic switch case we could map the original memory @@ -150,13 +228,6 @@ private: template void findCodePointers(const unsigned char *Start, const unsigned char *End); - bool isExecutableAddress(uint64_t Address) { - for (std::pair Range : ExecutableRanges) - if (Range.first <= Address && Address < Range.second) - return true; - return false; - } - void harvest(); void handleSumJump(llvm::Instruction *SumJump); @@ -184,6 +255,9 @@ private: std::vector& Segments; Architecture& SourceArchitecture; + + std::set ReliablePCs; + bool EnableOSRA; }; #endif // _JUMPTARGETMANAGER_H diff --git a/main.cpp b/main.cpp index 00354e165..878d32872 100644 --- a/main.cpp +++ b/main.cpp @@ -41,6 +41,7 @@ struct ProgramParameters { const char *DebugPath; const char *LinkingInfoPath; const char *CoveragePath; + bool NoOSRA; }; using LibraryDestructor = GenericFunctor; @@ -140,6 +141,8 @@ static int parseArgs(int Argc, const char *Argv[], OPT_STRING('d', "debug", &DebugLoggingString, "enable verbose logging."), + OPT_BOOLEAN('O', "no-osra", &Parameters->NoOSRA, + "disable OSRA"), OPT_END(), }; @@ -236,7 +239,8 @@ int main(int argc, const char *argv[]) { Parameters.DebugInfo, std::string(Parameters.DebugPath), std::string(Parameters.LinkingInfoPath), - std::string(Parameters.CoveragePath)); + std::string(Parameters.CoveragePath), + !Parameters.NoOSRA); Generator.translate(Parameters.EntryPointAddress, "root"); diff --git a/osra.cpp b/osra.cpp new file mode 100644 index 000000000..2d0974582 --- /dev/null +++ b/osra.cpp @@ -0,0 +1,1748 @@ +/// \file +/// \brief + +// Standard includes +#include +#include +#include + +// LLVM includes +#include "llvm/Analysis/ConstantFolding.h" +#include "llvm/IR/AssemblyAnnotationWriter.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/Module.h" +#include "llvm/Support/FormattedStream.h" +#include "llvm/Support/raw_os_ostream.h" +#include "llvm/Pass.h" + +// Local includes +#include "debug.h" +#include "revamb.h" +#include "ir-helpers.h" +#include "osra.h" + +using namespace llvm; + +using Predicate = CmpInst::Predicate; +using OSR = OSRAPass::OSR; +using BoundedValue = OSRAPass::BoundedValue; +using CE = ConstantExpr; +using CI = ConstantInt; +using std::pair; +using std::make_pair; +using std::numeric_limits; + +const BoundedValue::MergeType AndMerge = BoundedValue::And; +const BoundedValue::MergeType OrMerge = BoundedValue::Or; + +template +static auto skip(unsigned ToSkip, C &Container) + -> iterator_range { + auto Begin = std::begin(Container); + while (ToSkip --> 0) + Begin++; + return make_range(Begin, std::end(Container)); +} + +char OSRAPass::ID = 0; + +static RegisterPass X("jt-from-code", + "JT From Code Pass", + false, + false); + +Constant *OSR::evaluate(Constant *Value, Type *Int64) const { + Constant *BaseC = CI::get(Int64, Base, BV->isSigned()); + Constant *FactorC = CI::get(Int64, Factor, BV->isSigned()); + + return CE::getAdd(BaseC, CE::getMul(FactorC, Value)); +} + +static bool isPositive(Constant *C, const DataLayout &DL) { + auto *Zero = CI::get(C->getType(), 0, true); + auto *Compare = CE::getCompare(CmpInst::ICMP_SGE, C, Zero); + return getConstValue(Compare, DL)->getLimitedValue(); +} + +uint64_t OSR::absFactor(Type *Int64, const DataLayout &DL) const { + auto *FactorConst = CI::get(Int64, Factor, BV->isSigned()); + if (BV->isSigned() && !isPositive(FactorConst, DL)) + FactorConst = CE::getNeg(FactorConst); + return getZExtValue(FactorConst, DL); +} + +pair OSR::boundaries(Type *Int64, + const DataLayout &DL) const { + Constant *Min = nullptr; + Constant *Max = nullptr; + std::tie(Min, Max) = BV->actualBoundaries(Int64); + Min = evaluate(Min, Int64); + Max = evaluate(Max, Int64); + + return { Min, Max }; +} + +static uint64_t combineImpl(unsigned Opcode, + bool Signed, + uint64_t N, + IntegerType *T, + Constant *Op, + const DataLayout &DL) { + auto *R = ConstantFoldInstOperands(Opcode, T, + { CI::get(T, N, Signed), Op }, + DL); + return getExtValue(R, Signed, DL); +} + +bool OSR::combine(unsigned Opcode, + Constant *Operand, + const DataLayout &DL) { + + auto *TheType = cast(Operand->getType()); + bool Multiplicative = !(Opcode == Instruction::Add + || Opcode == Instruction::Sub); + bool Signed = (Opcode == Instruction::SDiv + || Opcode == Instruction::AShr); + + Operand = getConstValue(Operand, DL); + + uint64_t OldValue = Base; + Base = combineImpl(Opcode, Signed, Base, TheType, Operand, DL); + bool Changed = Base != OldValue; + + if (Multiplicative) { + OldValue = Factor; + Factor = combineImpl(Opcode, Signed, Factor, TheType, Operand, DL); + Changed |= OldValue != Factor; + } + + return Changed; +} + +class OSRAnnotationWriter : public AssemblyAnnotationWriter { +public: + OSRAnnotationWriter(OSRAPass &JTFC) : JTFC(JTFC) { } + + virtual void emitInstructionAnnot(const Instruction *I, + formatted_raw_ostream &Output) { + JTFC.describe(Output, I); + } + + virtual void emitBasicBlockStartAnnot(const BasicBlock *BB, + formatted_raw_ostream &Output) { + JTFC.describe(Output, BB); + } + +private: + OSRAPass &JTFC; +}; + +void OSR::describe(formatted_raw_ostream &O) const { + O << "[" << static_cast(Base) + << " + " << static_cast(Factor) << " * x, with x = "; + if (BV == nullptr) + O << "null"; + else + BV->describe(O); + O << "]"; +} + +void BoundedValue::describe(formatted_raw_ostream &O) const { + if (Negated) + O << "NOT "; + + O << "("; + O << Value; + if (Weak) + O << "*"; + O << ", "; + + switch (Sign) { + case UnknownSignedness: + O << "?"; + break; + case Signed: + O << "s"; + break; + case Unsigned: + O << "u"; + break; + case InconsistentSignedness: + O << "*"; + break; + } + + if (Bottom) { + O << ", bottom"; + } else if (Sign != UnknownSignedness) { + O << ", "; + if (LowerBound == lowerExtreme()) { + O << "min"; + } else { + O << LowerBound; + } + + O << ", "; + + if (UpperBound == upperExtreme()) { + O << "max"; + } else { + O << UpperBound; + } + } + + O << ")"; +} + +void OSRAPass::describe(formatted_raw_ostream &O, + const BasicBlock *BB) const { + BVs.describe(O, BB); +} + +void OSRAPass::describe(formatted_raw_ostream &O, + const Instruction *I) const { + auto OSRIt = OSRs.find(I); + auto ConstraintsIt = Constraints.find(I); + + if (OSRIt == OSRs.end() && ConstraintsIt == Constraints.end()) + return; + + if (OSRIt != OSRs.end()) { + O << " ; "; + OSRIt->second.describe(O); + O << "\n"; + } + + if (ConstraintsIt != Constraints.end()) { + O << " ; "; + for (auto Constraint : ConstraintsIt->second) { + Constraint.describe(O); + O << " "; + } + O << "\n"; + } +} + +Constant *OSR::solveEquation(Constant *KnownTerm, + bool CeilingRounding, + const DataLayout &DL) { + // (KnownTerm - Base) udiv Factor + bool IsSigned = BV->isSigned(); + + auto *BaseConst = CI::get(KnownTerm->getType(), Base, IsSigned); + auto *Numerator = CE::getSub(KnownTerm, BaseConst); + auto *Denominator = CI::get(KnownTerm->getType(), Factor, IsSigned); + + Constant *Remainder = nullptr; + Constant *Division = nullptr; + if (IsSigned) { + Remainder = CE::getSRem(Numerator, Denominator); + Division = CE::getSDiv(Numerator, Denominator); + } else { + Remainder = CE::getURem(Numerator, Denominator); + Division = CE::getUDiv(Numerator, Denominator); + } + + bool HasRemainder = getConstValue(Remainder, DL)->getLimitedValue() != 0; + if (CeilingRounding && HasRemainder) + Division = CE::getAdd(Division, CI::get(Division->getType(), 1)); + + return Division; +} + +OSR OSRAPass::createOSR(Value *V, BasicBlock *BB) { + auto OtherOSRIt = OSRs.find(V); + if (OtherOSRIt != OSRs.end()) + return switchBlock(OtherOSRIt->second, BB); + else + return OSR(&BVs.get(BB, V)); +} + +/// Helper function to check if two BV vectors are identical +static bool differ(SmallVector &Old, + SmallVector &New) { + if (Old.size() != New.size()) + return true; + + for (auto &OldConstraint : Old) { + bool Found = false; + for (auto &NewConstraint : New) { + if (OldConstraint.value() == NewConstraint.value()) { + Found = true; + if (!(OldConstraint == NewConstraint)) + return true; + } + } + + if (!Found) + return true; + } + + return false; +} + +template +static bool mergeBVVectors(OSRAPass::BVVector &Base, + OSRAPass::BVVector &New, + const DataLayout &DL, + Type *Int64) { + bool Result = false; + // Merge the two BV vectors + for (auto &NewConstraint : New) { + bool Found = false; + for (auto &BaseConstraint : Base) { + if (NewConstraint.value() == BaseConstraint.value()) { + Result |= BaseConstraint.merge(NewConstraint, DL, Int64); + Found = true; + break; + } + } + + if (!Found) { + Result = true; + Base.push_back(NewConstraint); + } + } + return Result; +} + +template +class VectorSet { +public: + void insert(T Element) { + if (Set.find(Element) == Set.end()) { + Set.insert(Element); + Queue.push(Element); + } + } + + bool empty() const { + return Queue.empty(); + } + + T pop() { + T Result = Queue.front(); + Queue.pop(); + Set.erase(Result); + return Result; + } + + size_t size() const { return Queue.size(); } +private: + std::set Set; + std::queue Queue; +}; + +/// Given an instruction, identifies, if possible, the constant operand. If +/// both operands are constant, it returns a Constant with the folded operation +/// and nullptr. If only one is constant, it return the constant and a reference +/// to the free operand. If none of the operands are constant returns { nullptr, +/// nullptr }. It also returns { nullptr, nullptr } if I is not commutative and +/// only the first operand is constant. +std::pair OSRAPass::identifyOperands(const Instruction *I, + Type *Int64, + const DataLayout &DL) { + assert(I->getNumOperands() == 2); + Value *FirstOp = I->getOperand(0); + Value *SecondOp = I->getOperand(1); + Constant *Constants[2] = { + dyn_cast(FirstOp), + dyn_cast(SecondOp) + }; + + // Is the first operand constant? + if (auto *Operand = dyn_cast(FirstOp)) { + auto OSRIt = OSRs.find(Operand); + if (OSRIt != OSRs.end() && OSRIt->second.isConstant()) + Constants[0] = CI::get(Int64, OSRIt->second.base()); + } + + // Is the second operand constant? + if (auto *Operand = dyn_cast(SecondOp)) { + auto OSRIt = OSRs.find(Operand); + if (OSRIt != OSRs.end() && OSRIt->second.isConstant()) + Constants[1] = CI::get(Int64, OSRIt->second.base()); + } + + // No operands are constant, or only the first one and the instruction is not + // commutative + if ((Constants[0] == nullptr && Constants[1] == nullptr) + || (Constants[0] != nullptr + && Constants[1] == nullptr + && !I->isCommutative())) + return { nullptr, nullptr }; + + // Both operands are constant, constant fold them + if (Constants[0] != nullptr && Constants[1] != nullptr) { + Instruction *Clone = I->clone(); + Clone->setOperand(0, Constants[0]); + Clone->setOperand(1, Constants[1]); + Constant *Result = ConstantFoldInstruction(Clone, DL); + if (isa(Result)) + return { nullptr, nullptr }; + else + return { Result, nullptr }; + } + + // Only one operand is constant + if (Constants[0] != nullptr) + return { Constants[0], SecondOp }; + else + return { Constants[1], FirstOp }; +} + +// TODO: check also undefined behaviors due to shifts +static bool isSupportedOperation(unsigned Opcode, + Constant *ConstantOp, + const DataLayout &DL) { + // Division by zero + if (((Opcode == Instruction::SDiv + || Opcode == Instruction::UDiv) + && getZExtValue(ConstantOp, DL) == 0)) + return false; + + // 128-bit operand + auto *ConstantOpTy = dyn_cast(ConstantOp->getType()); + if (ConstantOpTy != nullptr && ConstantOpTy->getBitWidth() > 64) + return false; + + return true; +} + +// Terminology: +// * OSR: Offseted Shifted Range, our main data flow value which represents the +// result of an instruction as another value, which lies withing a +// certain range of values, multiplied by a factor and with an +// offset, e.g. 100 + 4 * x, with 0 < x < 4. +// * free value: a value we can't represent as an OSR of another value +// * bounded variable (or BV): a free value and the range within which it lies. +bool OSRAPass::runOnFunction(Function &F) { + const DataLayout DL = F.getParent()->getDataLayout(); + + auto *Int64 = Type::getInt64Ty(F.getParent()->getContext()); + using UpdateFunc = std::function; + + std::set BlockBlackList; + for (auto &BB : F) { + if (!BB.empty()) { + if (auto *Call = dyn_cast(&*BB.begin())) { + Function *Callee = Call->getCalledFunction(); + if (Callee != nullptr && Callee->getName() == "newpc") + break; + } + } + BlockBlackList.insert(&BB); + } + + // Cleanup all the data + OSRs.clear(); + BVs = BVMap(&BlockBlackList, &DL, Int64); + Constraints.clear(); + + // Initialize the WorkList with all the instructions in the function + VectorSet WorkList; + auto &BBList = F.getBasicBlockList(); + for (auto &BB : make_range(BBList.begin(), BBList.end())) + if (BlockBlackList.find(&BB) == BlockBlackList.end()) + for (auto &I : make_range(BB.begin(), BB.end())) + WorkList.insert(&I); + + // TODO: make these member functions + auto InBlackList = [&BlockBlackList] (BasicBlock *BB) { + return BlockBlackList.find(BB) != BlockBlackList.end(); + }; + + auto EnqueueUsers = [&BlockBlackList, &WorkList] (Instruction *I) { + for (User *U : I->users()) + if (auto *UI = dyn_cast(U)) + if (BlockBlackList.find(UI->getParent()) == BlockBlackList.end()) { + WorkList.insert(UI); + } + }; + + auto PropagateConstraints = [this, &EnqueueUsers] (Instruction *I, + Value *Operand, + UpdateFunc Updater) { + // We want to propagate contraints through zero-extensions + if (auto *OperandInst = dyn_cast(Operand)) { + auto OperandConstraintIt = Constraints.find(OperandInst); + auto InstrConstraintIt = Constraints.find(I); + + // Does the operand have constraints? + if (OperandConstraintIt != Constraints.end()) { + auto New = Updater(OperandConstraintIt->second); + + // Does the instruction already had a constraint? + if (InstrConstraintIt != Constraints.end()) { + // Did the constraint changed? + if (!differ(New, InstrConstraintIt->second)) + return; + + Constraints.erase(InstrConstraintIt); + } + + Constraints.insert({ I, New }); + EnqueueUsers(I); + } + } + }; + + while (!WorkList.empty()) { + Instruction *I = WorkList.pop(); + + // TODO: create a member function for each group of opcodes + unsigned Opcode = I->getOpcode(); + switch (Opcode) { + case Instruction::Add: + case Instruction::Sub: + case Instruction::Mul: + case Instruction::Shl: + case Instruction::SDiv: + case Instruction::UDiv: + case Instruction::LShr: + case Instruction::AShr: + { + // Check if it's a free value + auto OldOSRIt = OSRs.find(I); + bool IsFree = OldOSRIt == OSRs.end(); + + Constant *ConstantOp = nullptr; + Value *OtherOp = nullptr; + std::tie(ConstantOp, OtherOp) = identifyOperands(I, Int64, DL); + + if (OtherOp == nullptr) { + if (ConstantOp != nullptr) { + // If OtherOp is nullptr but ConstantOp is not it means we were able + // to fold the operation in a constant + if (!IsFree) + OSRs.erase(I); + OSRs.emplace(make_pair(I, OSR(getZExtValue(ConstantOp, DL)))); + EnqueueUsers(I); + } + + // In any case, break + break; + } + + // Get or create an OSR for the non-constant operator, this + // will be our starting point + OSR NewOSR = createOSR(OtherOp, I->getParent()); + const Value *OldValue = OldOSRIt->second.boundedValue()->value(); + if (!IsFree + && !OldOSRIt->second.isConstant() + && NewOSR.isRelativeTo(OldValue)) { + break; + } + + // Check we're not depending on ourselves, if we are leave us as a free + // value + if (NewOSR.isRelativeTo(I)) { + assert(IsFree); + break; + } + + // TODO: this is probably a bad idea + if (NewOSR.boundedValue()->isBottom()) { + if (!IsFree) + OSRs.erase(OldOSRIt); + break; + } + + // Update signedness information if the given operation is + // sign-aware + if (Opcode == Instruction::SDiv + || Opcode == Instruction::UDiv + || Opcode == Instruction::LShr + || Opcode == Instruction::AShr) { + BVs.setSignedness(I->getParent(), + NewOSR.boundedValue()->value(), + Opcode == Instruction::SDiv + || Opcode == Instruction::AShr); + } + + bool Changed = true; + // Check for undefined behaviors + if (!isSupportedOperation(Opcode, ConstantOp, DL)) { + NewOSR = OSR(&BVs.get(I->getParent(), I)); + } else { + // Combine the base OSR with the new operation + Changed = NewOSR.combine(Opcode, ConstantOp, DL); + } + + // Check if the OSR has changed + if (IsFree || Changed) { + // Update the OSR and enqueue all I's uses + if (!IsFree) + OSRs.erase(I); + OSRs.emplace(make_pair(I, NewOSR)); + EnqueueUsers(I); + } + + break; + } + case Instruction::ICmp: + { + // TODO: this part is quite ugly, try to improve it + auto *Comparison = cast(I); + Predicate P = Comparison->getPredicate(); + + Constant *ConstOp = nullptr; + Value *FreeOpValue = nullptr; + Instruction *FreeOp = nullptr; + std::tie(ConstOp, FreeOpValue) = identifyOperands(I, Int64, DL); + if (FreeOpValue != nullptr) { + FreeOp = dyn_cast(FreeOpValue); + if (FreeOp == nullptr) + break; + } + + // Comparison for equality and inequality are handled to propagate + // constraints in case of test of the result of a comparison (e.g., (x < + // 3) == 0). + if (ConstOp != nullptr && FreeOp != nullptr + && Constraints.find(FreeOp) != Constraints.end() + && (P == CmpInst::ICMP_EQ || P == CmpInst::ICMP_NE)) { + // If we're comparing with 0 for equality or inequality and the + // non-constant operand has constraints, propagate them flipping them + // (if necessary). + if (getZExtValue(ConstOp, DL) == 0) { + + if (P == CmpInst::ICMP_EQ) { + PropagateConstraints(I, FreeOp, [] (BVVector &Constraints) { + BVVector Result = Constraints; + // TODO: This is wrong! !(a & b) == !a || !b, + // not !a && !b + for (auto &Constraint : Result) + Constraint.flip(); + return Result; + }); + } else { + PropagateConstraints(I, FreeOp, [] (BVVector &Constraints) { + return Constraints; + }); + } + + // Do not proceed + break; + } + } + + // Compute a new constraint + // Check the comparison operator is a supported one + if (P != CmpInst::ICMP_UGT + && P != CmpInst::ICMP_UGE + && P != CmpInst::ICMP_SGT + && P != CmpInst::ICMP_SGE + && P != CmpInst::ICMP_ULT + && P != CmpInst::ICMP_ULE + && P != CmpInst::ICMP_SLT + && P != CmpInst::ICMP_SLE + && P != CmpInst::ICMP_EQ + && P != CmpInst::ICMP_NE) + break; + + auto OldBVsIt = Constraints.find(I); + bool HasConstraints = OldBVsIt != Constraints.end(); + BVVector NewConstraints; + + if (FreeOp == nullptr) { + if (ConstOp == nullptr) { + // Both operands are free, give up + + // TODO: are we sure this is what we want? + if (HasConstraints) + Constraints.erase(OldBVsIt); + HasConstraints = false; + break; + } else { + // FreeOpValue is nullptr but ConstOp is not: we were able to fold + // the operation into a constant + + if (getZExtValue(ConstOp, DL) != 0) { + // The comparison holds, we're saying nothing useful (e.g. 2 < 3), + // remove any constraint + if (HasConstraints) + Constraints.erase(OldBVsIt); + HasConstraints = false; + } else { + // The comparison does not hold, move to bottom all the involved + // BVs + + auto *FirstOp = dyn_cast(I->getOperand(0)); + if (FirstOp != nullptr) { + auto FirstOSRIt = OSRs.find(FirstOp); + if (FirstOSRIt != OSRs.end()) { + auto FirstOSR = FirstOSRIt->second; + if (!FirstOSR.isConstant()) + NewConstraints.push_back(*FirstOSR.boundedValue()); + } + } + + if (auto *SecondOp = dyn_cast(I->getOperand(1))) { + auto SecondOSRIt = OSRs.find(SecondOp); + if (SecondOSRIt != OSRs.end()) { + auto SecondOSR = SecondOSRIt->second; + if (!SecondOSRIt->second.isConstant()) + NewConstraints.push_back(*SecondOSR.boundedValue()); + } + } + + for (auto &Constraint : NewConstraints) + Constraint.setBottom(); + + } + } + + } else { + // We have a constant operand and a free one + + BasicBlock *BB = I->getParent(); + OSR BaseOp = createOSR(FreeOp, BB); + + if (BaseOp.boundedValue()->isBottom() || BaseOp.isRelativeTo(I)) + break; + + // Notify the BV about the sign we're going to use + bool IsSigned = Comparison->isSigned(); + BVs.setSignedness(BB, + BaseOp.boundedValue()->value(), + IsSigned); + + // Setting the sign might lead to bottom + if (BaseOp.boundedValue()->isBottom()) + break; + + // Create a copy of the current value of the BV + BoundedValue NewBV = *(BaseOp.boundedValue()); + + // Solve the equation to obtain the new boundary value + // x < 1.5 == x < 2 (Ceiling) + // x <= 1.5 == x <= 1 (Floor) + // x > 1.5 == x > 1 (Floor) + // x >= 1.5 == x >= 2 (Ceiling) + bool RoundUp = (P == CmpInst::ICMP_UGE + || P == CmpInst::ICMP_SGE + || P == CmpInst::ICMP_ULT + || P == CmpInst::ICMP_SLT); + + Constant *NewBoundC = BaseOp.solveEquation(ConstOp, RoundUp, DL); + uint64_t NewBound = getExtValue(NewBoundC, IsSigned, DL); + + using BV = BoundedValue; + switch (P) { + case CmpInst::ICMP_UGT: + case CmpInst::ICMP_UGE: + case CmpInst::ICMP_SGT: + case CmpInst::ICMP_SGE: + if (Comparison->isFalseWhenEqual()) + NewBound++; + + NewBV.merge(BV::createGE(NewBV.value(), NewBound, IsSigned), + DL, Int64); + break; + case CmpInst::ICMP_ULT: + case CmpInst::ICMP_ULE: + case CmpInst::ICMP_SLT: + case CmpInst::ICMP_SLE: + if (Comparison->isFalseWhenEqual()) + NewBound--; + + NewBV.merge(BV::createLE(NewBV.value(), NewBound, IsSigned), + DL, Int64); + break; + case CmpInst::ICMP_EQ: + NewBV.merge(BV::createEQ(NewBV.value(), NewBound, IsSigned), + DL, Int64); + break; + case CmpInst::ICMP_NE: + NewBV.merge(BV::createNE(NewBV.value(), NewBound, IsSigned), + DL, Int64); + break; + default: + assert(false); + break; + } + + NewConstraints = { NewBV }; + } + + bool Changed = true; + + // Check against the old constraints associated with this comparison + if (HasConstraints) { + BVVector &OldBVsVector = OldBVsIt->second; + if (NewConstraints.size() == OldBVsVector.size()) { + bool Different = false; + auto OldIt = OldBVsVector.begin(); + auto NewIt = NewConstraints.begin(); + + // Loop over all the elements until a different one is found or we + // reached the end + while (!Different && OldIt != OldBVsVector.end()) { + Different |= *OldIt != *NewIt; + OldIt++; + NewIt++; + } + + Changed = Different; + } + } + + // If something changed replace the BV vector and re-enqueue all the + // users + if (Changed) { + Constraints[I] = NewConstraints; + EnqueueUsers(I); + } + + break; + } + case Instruction::ZExt: + { + PropagateConstraints(I, I->getOperand(0), [] (BVVector &BV) { + return BV; + }); + break; + } + case Instruction::And: + case Instruction::Or: + { + Instruction *FirstOperand = dyn_cast(I->getOperand(0)); + Instruction *SecondOperand = dyn_cast(I->getOperand(1)); + if (FirstOperand == nullptr || SecondOperand == nullptr) + break; + + auto FirstConstraintIt = Constraints.find(FirstOperand); + auto SecondConstraintIt = Constraints.find(SecondOperand); + + // We can merge the BVs only if both operands have one + if (FirstConstraintIt == Constraints.end() + || SecondConstraintIt == Constraints.end()) + break; + + // Initialize the new boundaries with the first operand + auto NewConstraints = FirstConstraintIt->second; + auto &OtherConstraints = SecondConstraintIt->second; + + if (Opcode == Instruction::And) + mergeBVVectors(NewConstraints, OtherConstraints, DL, Int64); + else + mergeBVVectors(NewConstraints, OtherConstraints, DL, Int64); + + bool Changed = true; + // If this instruction already had constraints, compare them with the + // new ones + auto OldConstraintsIt = Constraints.find(I); + if (OldConstraintsIt != Constraints.end()) + Changed = differ(OldConstraintsIt->second, NewConstraints); + + // If something changed, register the new constraints and re-enqueue all + // the users of the instruction + if (Changed) { + Constraints[I] = NewConstraints; + EnqueueUsers(I); + } + + break; + } + case Instruction::Br: + { + auto *Branch = cast(I); + + // Unconditional branches bring no useful information + if (Branch->isUnconditional()) + break; + + auto *Condition = dyn_cast(Branch->getCondition()); + if (Condition == nullptr) + break; + + // Were we able to handle the condition? + auto BranchConstraintsIt = Constraints.find(Condition); + if (BranchConstraintsIt == Constraints.end()) + break; + + // Take a reference to the constraints, and produce a complementary + // version + auto &BranchConstraints = BranchConstraintsIt->second; + BVVector FlippedBranchConstraints = BranchConstraintsIt->second; + // TODO: This is wrong! !(a & b) == !a || !b, not !a && !b + for (auto &BranchConstraint : FlippedBranchConstraints) + BranchConstraint.flip(); + + // Create and initialize the worklist with the positive constraints for + // the true branch, and the negated constraints for the false branch + struct WLEntry { + WLEntry(BasicBlock *Target, + BasicBlock *Origin, + BVVector Constraints) : + Target(Target), Origin(Origin), Constraints(Constraints) { } + + BasicBlock *Target; + BasicBlock *Origin; + BVVector Constraints; + }; + + std::vector ConstraintsWL; + if (!InBlackList(Branch->getSuccessor(0))) { + ConstraintsWL.push_back(WLEntry(Branch->getSuccessor(0), + Branch->getParent(), + BranchConstraints)); + } + + if (!InBlackList(Branch->getSuccessor(1))) { + ConstraintsWL.push_back(WLEntry(Branch->getSuccessor(1), + Branch->getParent(), + FlippedBranchConstraints)); + } + + // Process the worklist + while (!ConstraintsWL.empty()) { + auto Entry = ConstraintsWL.back(); + ConstraintsWL.pop_back(); + + assert(BlockBlackList.find(Entry.Target) == BlockBlackList.end()); + + // Merge each changed bound with the existing one + for (auto ConstraintIt = Entry.Constraints.begin(); + ConstraintIt != Entry.Constraints.end();) { + + auto Result = BVs.update(Entry.Target, Entry.Origin, *ConstraintIt); + bool Changed = Result.first; + BoundedValue &NewBV = Result.second; + + if (Changed) { + // From now we propagate the updated constraint + *ConstraintIt = NewBV; + ConstraintIt++; + } else { + ConstraintIt = Entry.Constraints.erase(ConstraintIt); + } + } + + // Look for instructions using constraints that have changed + for (auto &ConstraintUser : *Entry.Target) { + // Avoid looking up instructions that simply cannot be there + auto Opcode = ConstraintUser.getOpcode(); + if (Opcode != Instruction::ICmp + && Opcode != Instruction::And + && Opcode != Instruction::Or) + continue; + + // Ignore instructions without an associated constraint + auto ConstraintIt = Constraints.find(&ConstraintUser); + if (ConstraintIt == Constraints.end()) + continue; + + // If it's using one of the changed variables, insert it in the + // worklist + BVVector &InstructionConstraints = ConstraintIt->second; + + bool NeedsUpdate = false; + for (auto &Constraint : Entry.Constraints) { + for (auto &InstructionConstraint : InstructionConstraints) { + if (InstructionConstraint.value() == Constraint.value()) { + NeedsUpdate = true; + break; + } + } + + if (NeedsUpdate) { + WorkList.insert(&ConstraintUser); + break; + } + + } + + } + + // Propagate the new constraints to the successors (except for the + // dispatcher) + auto Successors = make_range(succ_begin(Entry.Target), + succ_end(Entry.Target)); + if (Entry.Constraints.size() != 0) + for (BasicBlock *Successor : Successors) + if (BlockBlackList.find(Successor) == BlockBlackList.end()) + ConstraintsWL.push_back(WLEntry(Successor, + Entry.Target, + Entry.Constraints)); + } + + break; + } + case Instruction::Store: + case Instruction::Load: + { + // Create the OSR to propagate + Value *Pointer = nullptr; + auto TheLoad = dyn_cast(I); + auto TheStore = dyn_cast(I); + + // TODO: rename SelfOSR (it's not always self) + OSR SelfOSR; + BVVector TheConstraints; + bool HasConstraints = false; + + if (TheLoad != nullptr) { + // It'a a load + + // If the load doesn't have an OSR associated (or it's associated to + // itself), propagate it forward + auto OSRIt = OSRs.find(I); + if (OSRIt != OSRs.end()) + break; + + Pointer = TheLoad->getPointerOperand(); + SelfOSR = OSR(&BVs.get(I->getParent(), I)); + } else { + // It's a store + assert(TheStore != nullptr); + Pointer = TheStore->getPointerOperand(); + Value *ValueOp = TheStore->getValueOperand(); + + if (auto *ConstantOp = dyn_cast(ValueOp)) { + // We're storing a constant, create a constant OSR + SelfOSR = OSR(getZExtValue(ConstantOp, DL)); + } else if (auto *ToStore = dyn_cast(ValueOp)) { + // Compute the OSR to propagate: either the one of the value to + // store, or a self-referencing one + auto OSRIt = OSRs.find(ToStore); + if (OSRIt != OSRs.end()) + SelfOSR = OSRIt->second; + else + SelfOSR = OSR(&BVs.get(I->getParent(), I)); + + // Check if the value we're storing has an constraints + auto ConstraintIt = Constraints.find(ToStore); + if (ConstraintIt != Constraints.end()) { + HasConstraints = true; + TheConstraints = ConstraintIt->second; + } + + } + + } + + // TODO: very important, factor the two followin block of codes, we + // can't handle the two propagation in parallel since OSR don't + // have a merge policy (and most stop on conflicts) while + // constraints have to be propagated and merged to all the load a + // certain loat or store can see. + + // Note: for simplicity, from now on comments will talk about "load + // instructions", however this code handles stores too. + { + // Initialize the work list with the instruction after the store + std::vector> ExploreWL; + ExploreWL.push_back(make_range(++I->getIterator(), + I->getParent()->end())); + + // TODO: can we remove Visited? + std::set Visited; + // Note: we don't insert in Visited the initial basic block, so it can + // get visisted again to consider the part before the load + // instruction. + + // Overtaken contains the list of all the loads that this Load + // overtakes. Keeping track of this allows us to overtake other loads + // which reads them. + std::set Overtaken; + Overtaken.insert(I); + // Conflicts contains the list of loads we're not able to overtake, + // which we'll have to move to top (i.e. make them indepent) + std::set Conflicts; + + while (!ExploreWL.empty()) { + auto R = ExploreWL.back(); + ExploreWL.pop_back(); + + auto *BB = R.begin()->getParent(); + assert(!BlockBlackList.count(BB)); + Visited.insert(BB); + + // Loop over the instructions from here to the end of the basic + // block + bool Stop = false; + for (Instruction &Inst : R) { + if (auto *Load = dyn_cast(&Inst)) { + // TODO: handle casts and the like + // Is it loading from the same address of our load? + if (Load->getPointerOperand() != Pointer) + continue; + + // Take the reference OSR (SelfOSR) and "contextualize" it in + // the current BasicBlock + OSR NewOSR = switchBlock(SelfOSR, BB); + + auto LoadOSRIt = OSRs.find(Load); + // Check if the instruction already has an OSR + if (LoadOSRIt != OSRs.end()) { + if (LoadOSRIt->second.isRelativeTo(Load) + || LoadOSRIt->second == NewOSR) { + // We already passed by here + Stop = true; + } else if (LoadOSRIt->second.isConstant()) { + // It's constant and different, we'll never be able to take + // it over + Stop = true; + } else { + // It's already expressed in terms of someone else, check if + // we have already took over that variable + auto *BV = LoadOSRIt->second.boundedValue(); + const Value *RelativeTo = BV->value(); + if (Overtaken.count(RelativeTo)) { + // We already overtook the load it is referring to, + // override safely + OSRs.erase(LoadOSRIt); + } else { + // We didn't overtake the load it is referring to (yet?), + // it's a potential conflict, register it, then stop. + Conflicts.insert(Load); + Stop = true; + } + } + } + + if (Stop) + break; + + // Insert the NewOSR in OSRs and mark the load as overtaken + OSRs.insert({ &Inst, NewOSR }); + Overtaken.insert(Load); + + // The OSR has changed, mark the load and its uses to be + // visited again + WorkList.insert(Load); + EnqueueUsers(Load); + + } else if (auto *Store = dyn_cast(&Inst)) { + // Check if this store might alias the memory area we're tracking + auto *PointerOp = Store->getPointerOperand(); + if (PointerOp == Pointer + || (!isa(PointerOp) + && !isa(PointerOp))) { + Stop = true; + break; + } + } + + } + + // If we didn't stop, enqueue all the non-blacklisted successors for + // exploration + if (!Stop) + for (auto *Successor : make_range(succ_begin(BB), succ_end(BB))) + if (!BlockBlackList.count(Successor) + && !Successor->empty() + && !Visited.count(Successor)) + ExploreWL.push_back(make_range(Successor->begin(), + Successor->end())); + + // When we have nothing more to explore, before giving up, check all + // the candidate conflicts to see if some of them are no longer + // conflicts, and, if so, re-enqueue them + if (ExploreWL.empty()) { + for (auto It = Conflicts.begin(); It != Conflicts.end();) { + LoadInst *Conflicting = *It; + auto ConflictOSRIt = OSRs.find(Conflicting); + assert(ConflictOSRIt != OSRs.end()); + auto *BV = ConflictOSRIt->second.boundedValue(); + const Value *RelativeTo = BV->value(); + if (Overtaken.count(RelativeTo)) { + auto *ConflictingBB = Conflicting->getParent(); + ExploreWL.push_back(make_range(Conflicting->getIterator(), + ConflictingBB->end())); + It = Conflicts.erase(It); + } else + It++; + } + } + + } // End of the worklist loop + + // At this point we propagated everything we could, the remaining + // elements in Conflicts are real conflicts, make them autonomous + for (LoadInst *Conflict : Conflicts) { + OSR FreeOSR = createOSR(Conflict, Conflict->getParent()); + auto ConflictOSRIt = OSRs.find(Conflict); + assert(ConflictOSRIt != OSRs.end()); + if (ConflictOSRIt->second != FreeOSR) { + OSRs.erase(ConflictOSRIt); + OSRs.insert({ Conflict, FreeOSR }); + + WorkList.insert(Conflict); + EnqueueUsers(Conflict); + } + } + } + + if (HasConstraints) { + // Initialize the work list with the instruction after the store + std::vector> ExploreWL; + ExploreWL.push_back(make_range(++I->getIterator(), + I->getParent()->end())); + + // TODO: can we remove Visited? + std::set Visited; + // Note: we don't insert in Visisted the initial basic block, so it + // can get visisted again to consider the part before the load + // instruction. + + while (!ExploreWL.empty()) { + auto R = ExploreWL.back(); + ExploreWL.pop_back(); + + auto *BB = R.begin()->getParent(); + assert(BlockBlackList.find(BB) == BlockBlackList.end()); + Visited.insert(BB); + + // Loop over the instructions from here to the end of the basic + // block + bool Stop = false; + for (Instruction &Inst : R) { + if (auto *Load = dyn_cast(&Inst)) { + // TODO: handle casts and the like + // Is it loading from the same address of our load? + if (Load->getPointerOperand() != Pointer) + continue; + + bool Changed = true; + + // Propagate the constraints + auto LoadConstraintIt = Constraints.find(Load); + if (LoadConstraintIt == Constraints.end()) { + // The load has no constraints, simply propagate the input + // ones + Constraints.insert({ &Inst, TheConstraints }); + } else { + // Merge the constraints (using the `or` logic) directly + // in-place in the load's BVVector + using BV = BoundedValue; + Changed = mergeBVVectors(LoadConstraintIt->second, + TheConstraints, + DL, + Int64); + } + + // If OSR or constraints have changed, mark the load and its + // uses to be visited again + if (Changed) { + WorkList.insert(Load); + EnqueueUsers(Load); + } + + } else if (auto *Store = dyn_cast(&Inst)) { + // Check if this store might alias the memory area we're + // tracking + auto *PointerOp = Store->getPointerOperand(); + if (PointerOp == Pointer + || (!isa(PointerOp) + && !isa(PointerOp))) { + Stop = true; + break; + } + } + + } + + // If we didn't stop, enqueue all the non-blacklisted successors for + // exploration + if (!Stop) + for (auto *Successor : make_range(succ_begin(BB), succ_end(BB))) + if (BlockBlackList.find(Successor) == BlockBlackList.end() + && !Successor->empty() + && Visited.find(Successor) == Visited.end()) + ExploreWL.push_back(make_range(Successor->begin(), + Successor->end())); + + } // End of the worklist loop + + } + + break; + } + default: + break; + } + } + + DBG("osr", { + BVs.prepareDescribe(); + raw_os_ostream OutputStream(dbg); + F.getParent()->print(OutputStream, new OSRAnnotationWriter(*this)); + }); + + return false; +} + +void OSRAPass::BVMap::describe(formatted_raw_ostream &O, + const BasicBlock *BB) const { + if (BBMap.find(BB) != BBMap.end()) + for (MapValue &MV : BBMap[BB]) { + O << " ; "; + + { + auto &BVO = MV.Summary; + O << "<"; + BVO.describe(O); + O << ">"; + } + + if (MV.Components.size() > 0) + O << " = "; + + for (auto &BVO : MV.Components) { + O << "<"; + O << (BVO.first != nullptr ? BVO.first->getName() : StringRef("")); + O << ", "; + BVO.second.describe(O); + O << "> || "; + } + + O << "\n"; + } + O << "\n"; +} + +std::pair OSRAPass::BVMap::update(BasicBlock *Target, + BasicBlock *Origin, + BoundedValue NewBV) { + auto Index = make_pair(Target, NewBV.value()); + auto MapIt = TheMap.find(Index); + bool Changed = true; + + MapValue *BVOVector = nullptr; + + // Have we ever seen this value for this basic block? + if (MapIt == TheMap.end()) { + // No, just insert it + MapValue NewBVOVector; + NewBVOVector.Components.push_back({ make_pair(Origin, NewBV) }); + BVOVector = &TheMap.insert({ Index, NewBVOVector }).first->second; + } else { + BVOVector = &MapIt->second; + + // Look for an entry with the given origin + BoundedValue *Base = nullptr; + for (BVWithOrigin &BVO : BVOVector->Components) + if (BVO.first == Origin) + Base = &BVO.second; + + // Did we ever see this Origin? + if (Base == nullptr) + BVOVector->Components.push_back({ Origin, NewBV }); + else + Changed = Base->merge(NewBV, *DL, Int64); + } + + // Re-merge all the entries + auto &Result = summarize(Target, BVOVector); + + return { Changed, Result }; +} + +BoundedValue &OSRAPass::BVMap::summarize(BasicBlock *Target, + MapValue *BVOVector) { + + if (BVOVector->Components.size() == 0) + return BVOVector->Summary; + + // Initialize the summary BV with the first BV + BVOVector->Summary = BVOVector->Components[0].second; + + unsigned PredecessorsCount = 0; + for (auto *Predecessor : make_range(pred_begin(Target), pred_end(Target))) + if (BlockBlackList->find(Predecessor) == BlockBlackList->end() + && pred_begin(Predecessor) != pred_end(Predecessor)) + PredecessorsCount++; + + // Do we have a constraint for each predecessor? + if (BVOVector->Components.size() == PredecessorsCount) { + // Yes, we can populate the summary by merging all the components + for (auto &BVO : skip(1, BVOVector->Components)) + BVOVector->Summary.merge(BVO.second, *DL, Int64); + } else { + // No, keep the summary at top + BVOVector->Summary.setTop(); + } + + return BVOVector->Summary; +} + +bool OSR::compare(unsigned short P, + Constant *C, + const DataLayout &DL, + Type *Int64) { + Constant *BaseConstant = CI::get(Int64, Base); + Constant *Compare = CE::getCompare(P, BaseConstant, C); + return getConstValue(Compare, DL)->getLimitedValue() != 0; +} + +void BoundedValue::setSignedness(bool IsSigned) { + // TODO: assert? + if (Bottom) + return; + + // If we're already inconsistent just return + if (Sign == InconsistentSignedness) + return; + + Signedness NewSign = IsSigned ? Signed : Unsigned; + if (Sign == UnknownSignedness) { + assert(LowerBound == 0 && UpperBound == 0); + Sign = NewSign; + + if (IsSigned) { + LowerBound = numeric_limits::min(); + UpperBound = numeric_limits::max(); + } else { + LowerBound = numeric_limits::min(); + UpperBound = numeric_limits::max(); + } + + } else if (Sign != NewSign) { + Sign = InconsistentSignedness; + // TODO: handle top case + if (LowerBound > numeric_limits::max() + || UpperBound > numeric_limits::max()) { + setBottom(); + } + } +} + +template +bool BoundedValue::merge(const BoundedValue &Other, + const DataLayout &DL, + Type *Int64) { + if (Bottom) + return false; + + if (Other.Bottom) { + setBottom(); + return true; + } + + if (isTop() && Other.isTop()) { + return false; + } else if (MT == And && isTop()) { + LowerBound = Other.LowerBound; + UpperBound = Other.UpperBound; + Sign = Other.Sign; + Negated = Other.Negated; + return true; + } else if (MT == And && Other.isTop()) { + return false; + } else if (MT == Or && isTop()) { + return false; + } else if (MT == Or && Other.isTop()) { + setTop(); + return true; + } + + setSignedness(Other.isSigned()); + if (Bottom) + return true; + + // TODO: reimplement all of this using a simple and sane range merging + // approach + + Predicate LE = isSigned() ? CmpInst::ICMP_SLE : CmpInst::ICMP_ULE; + Predicate LT = isSigned() ? CmpInst::ICMP_SLT : CmpInst::ICMP_ULT; + Predicate GE = isSigned() ? CmpInst::ICMP_SGE : CmpInst::ICMP_UGE; + Predicate GT = isSigned() ? CmpInst::ICMP_SGT : CmpInst::ICMP_UGT; + + auto Compare = [&Int64, &DL] (uint64_t A, Predicate P, int64_t B) { + Constant *Compare = CE::getCompare(P, CI::get(Int64, A), CI::get(Int64, B)); + return getZExtValue(Compare, DL) != 0; + }; + + const BoundedValue *LeftmostOp = this; + const BoundedValue *RightmostOp = &Other; + + // Check that the LB of the lefmost is <= of the rightmost LB + if (Compare(LeftmostOp->LowerBound, GT, RightmostOp->LowerBound)) + std::swap(LeftmostOp, RightmostOp); + + // If they both start at the same point, LeftmostOp is the largest + if (Compare(LeftmostOp->LowerBound, CmpInst::ICMP_EQ, RightmostOp->LowerBound) + && Compare(RightmostOp->UpperBound, GT, LeftmostOp->UpperBound)) + std::swap(LeftmostOp, RightmostOp); + + enum { + Disjoint, + Overlapping + } Overlap; + + bool LowerLT = Compare(LeftmostOp->LowerBound, LT, RightmostOp->LowerBound); + bool LowerLE = Compare(LeftmostOp->LowerBound, LE, RightmostOp->LowerBound); + bool UpperGT = Compare(LeftmostOp->UpperBound, GT, RightmostOp->UpperBound); + bool UpperGE = Compare(LeftmostOp->UpperBound, GE, RightmostOp->UpperBound); + bool StrictlyIncluded = LowerLT && UpperGT; + bool Included = LowerLE && UpperGE; + + if (Compare(LeftmostOp->UpperBound, LT, RightmostOp->LowerBound)) + Overlap = Disjoint; + else + Overlap = Overlapping; + + const BoundedValue *NegatedOp = nullptr; + const BoundedValue *NonNegatedOp = nullptr; + enum { + NoNegated, + OneNegated, + BothNegated + } Operands; + + if (!Negated && !Other.Negated) { + Operands = NoNegated; + } else if (Negated && Other.Negated) { + Operands = BothNegated; + } else { + Operands = OneNegated; + if (Negated) { + NegatedOp = this; + NonNegatedOp = &Other; + } else { + NegatedOp = &Other; + NonNegatedOp = this; + } + } + + uint64_t OldLowerBound = LowerBound; + uint64_t OldUpperBound = UpperBound; + bool OldNegated = Negated; + + // In the following table we report all the possible situations and the + // relative result we produce: + // + // type overlap op1 op2 result + // ====================================== + // and disjoint + + bottom + // and disjoint + - op1 + // and disjoint - - bottom + // and overlapping + + intersection + // and overlapping + - op1-op2 + // and overlapping - - !union + // or disjoint + + bottom + // or disjoint + - op2 + // or disjoint - - top + // or overlapping + + union + // or overlapping + - !(op2-op1) + // or overlapping - - !intersection + // + + bool Changed = false; + if (MT == And) { + switch(Overlap) { + case Disjoint: + switch (Operands) { + case NoNegated: + setBottom(); + Changed = true; + break; + case BothNegated: + if (LeftmostOp->LowerBound == LeftmostOp->lowerExtreme() + && RightmostOp->UpperBound == RightmostOp->upperExtreme()) { + std::tie(LowerBound, UpperBound) = make_pair(LeftmostOp->UpperBound, + RightmostOp->LowerBound); + Negated = false; + break; + } + + setBottom(); + Changed = true; + break; + case OneNegated: + // Assign to NotNegated + if (this != NonNegatedOp) { + LowerBound = Other.LowerBound; + UpperBound = Other.UpperBound; + Negated = Other.Negated; + } + break; + } + break; + case Overlapping: + switch (Operands) { + case NoNegated: + // Intersection + setBound(CI::get(Int64, Other.LowerBound), DL); + if (!Bottom) + setBound(CI::get(Int64, Other.UpperBound), DL); + Negated = false; + break; + case OneNegated: + // TODO: If one of the two is strictly included go to bottom + if (StrictlyIncluded + || (LowerBound == Other.LowerBound + && UpperBound == Other.UpperBound) + || (Included && LeftmostOp == NegatedOp)) { + setBottom(); + Changed = true; + break; + } + + // NonNegated - Negated + // [5,10] - ![8,12] => NonNegated.Up = Negated.Down - 1 + // [5,10] - ![1,12] == [0,10] - ([_,0] | [13,_]) + // [5,10] - ![0,7] => NonNegated.Down = Negated.Up + 1 + + // [5,10] - ![4,7] + // [5,10] - ![5,7] + // [5,10] - ![6,12] + // Check if NonNegated is after Negated + uint64_t NewLowerBound, NewUpperBound; + if (Compare(NonNegatedOp->LowerBound, GE, NegatedOp->LowerBound)) { + NewLowerBound = NegatedOp->UpperBound + 1; + NewUpperBound = NonNegatedOp->UpperBound; + } else { + NewLowerBound = NonNegatedOp->LowerBound; + NewUpperBound = NegatedOp->LowerBound - 1; + } + LowerBound = NewLowerBound; + UpperBound = NewUpperBound; + Negated = false; + break; + case BothNegated: + // Negated union + setBound(CI::get(Int64, Other.LowerBound), DL); + if (!Bottom) + setBound(CI::get(Int64, Other.UpperBound), DL); + Negated = true; + break; + } + break; + } + } else if (MT == Or) { + switch(Overlap) { + case Disjoint: + switch (Operands) { + case NoNegated: + setBottom(); + Changed = true; + break; + case OneNegated: + // Assign to Negated + if (this != NegatedOp) { + LowerBound = Other.LowerBound; + UpperBound = Other.UpperBound; + Negated = Other.Negated; + } + break; + case BothNegated: + setTop(); + Changed = true; + break; + } + break; + case Overlapping: + switch (Operands) { + case NoNegated: + setBound(CI::get(Int64, Other.LowerBound), DL); + if (!Bottom) + setBound(CI::get(Int64, Other.UpperBound), DL); + Negated = true; + break; + case OneNegated: + // TODO: comment this + if (StrictlyIncluded) { + if (LeftmostOp == NonNegatedOp) + setTop(); + else + setBottom(); + Changed = true; + break; + } + + if ((LowerBound == Other.LowerBound + && UpperBound == Other.UpperBound) + || (Included && LeftmostOp == NonNegatedOp)) { + setTop(); + Changed = true; + break; + } + + // ![5,25] || [6,30] + // ![5,25] || [5,10] + // Check if NonNegated is before Negated + uint64_t NewLowerBound, NewUpperBound; + if (Compare(NonNegatedOp->LowerBound, LE, NegatedOp->LowerBound)) { + NewLowerBound = NonNegatedOp->UpperBound + 1; + NewUpperBound = NegatedOp->UpperBound; + } else { + NewLowerBound = NegatedOp->LowerBound; + NewUpperBound = NonNegatedOp->LowerBound - 1; + } + LowerBound = NewLowerBound; + UpperBound = NewUpperBound; + Negated = true; + break; + case BothNegated: + setBound(CI::get(Int64, Other.LowerBound), DL); + if (!Bottom) + setBound(CI::get(Int64, Other.UpperBound), DL); + Negated = true; + break; + } + break; + } + } + + Changed |= (OldLowerBound != LowerBound + || OldUpperBound != UpperBound + || OldNegated != Negated); + + assert(Compare(LowerBound, LE, UpperBound)); + + return Changed; +} + +// Note: this function is implemented with lower bound restriction in mind, with +// additional changes to support bound enlargement (logical `or`) or work on the +// upper bound just set the template arguments appopriately +template +bool BoundedValue::setBound(Constant *NewValue, const DataLayout &DL) { + assert(Sign != UnknownSignedness && !Bottom); + + uint64_t &Bound = B == Lower ? LowerBound : UpperBound; + + // Create a Constant for the current bound + Constant *OldValue = CI::get(NewValue->getType(), + Bound, + isSigned()); + + // If the signedness is inconsistent, check that the new value lies in the + // signed positive area, otherwise go to bottom + // Note: OldValue should already be in this range, thanks to `setSignedness`. + if (Sign == InconsistentSignedness && !isPositive(NewValue, DL)) { + setBottom(); + return true; + } + + // Update the lower bound only if NewValue > OldValue + Predicate CompOp = (isSigned() ? + CmpInst::ICMP_SGT : + CmpInst::ICMP_UGT); + + // If we want a logical or, flip the direction of the comparison + if (Type == Or) + CompOp = CmpInst::getSwappedPredicate(CompOp); + + if (B == Upper) + CompOp = CmpInst::getSwappedPredicate(CompOp); + + // Perform the comparison and, in case, update the LowerBound + auto *Compare = CE::getCompare(CompOp, NewValue, OldValue); + if (getConstValue(Compare, DL)->getLimitedValue()) { + if (isSigned()) + Bound = getSExtValue(NewValue, DL); + else + Bound = getZExtValue(NewValue, DL); + return true; + } + return false; +} diff --git a/osra.h b/osra.h new file mode 100644 index 000000000..fd1806eef --- /dev/null +++ b/osra.h @@ -0,0 +1,478 @@ +#ifndef _OSRA_H +#define _OSRA_H + +// Standard includes +#include +#include +#include +#include +#include +#include + +// Forward declarations +namespace llvm { +class BasicBlock; +class formatted_raw_ostream; +class Function; +class Instruction; +class LLVMContext; +class LoadInst; +class Module; +class SwitchInst; +class StoreInst; +class Value; +} + +class OSRAPass : public llvm::FunctionPass { +public: + static char ID; + + OSRAPass() : llvm::FunctionPass(ID) { } + + bool runOnFunction(llvm::Function &F) override; + + void describe(llvm::formatted_raw_ostream &O, + const llvm::Instruction *I) const; + void describe(llvm::formatted_raw_ostream &O, + const llvm::BasicBlock *BB) const; + +public: + class BoundedValue { + public: + BoundedValue(const llvm::Value *V) : + Value(V), + LowerBound(0), + UpperBound(0), + Sign(UnknownSignedness), + Bottom(false), + Negated(false), + Weak(false) { } + + BoundedValue() : + Value(nullptr), + LowerBound(0), + UpperBound(0), + Sign(UnknownSignedness), + Bottom(false), + Negated(false), + Weak(false) { } + + // TODO: update users in case the sign changed + void setSignedness(bool IsSigned); + + void describe(llvm::formatted_raw_ostream &O) const; + + enum MergeType { And, Or }; + enum Bound { Lower, Upper }; + + bool exclude(llvm::Constant *ToExclude, + const llvm::DataLayout &DL, + llvm::Type *Int64); + + bool isUninitialized() const { return Sign == UnknownSignedness; } + + template + bool merge(const BoundedValue &Other, + const llvm::DataLayout &DL, + llvm::Type *Int64); + + template + bool setBound(llvm::Constant *NewValue, const llvm::DataLayout &DL); + + const llvm::Value *value() const { return Value; } + + bool isSigned() const { + assert(Sign != UnknownSignedness && !Bottom); + return Sign != Unsigned; + } + + llvm::Constant *lower(llvm::Type *Int64) const { + return llvm::ConstantInt::get(Int64, LowerBound, isSigned()); + } + + llvm::Constant *upper(llvm::Type *Int64) const { + return llvm::ConstantInt::get(Int64, UpperBound, isSigned()); + } + + /// Returns the BoundedValue bounds considering negation + std::pair + actualBoundaries(llvm::Type *Int64) const { + + using CI = llvm::ConstantInt; + if (!Negated) + return std::make_pair(lower(Int64), upper(Int64)); + else if (LowerBound == lowerExtreme()) + return std::make_pair(CI::get(Int64, UpperBound + 1, isSigned()), + CI::get(Int64, upperExtreme(), isSigned())); + else if (UpperBound == upperExtreme()) + return std::make_pair(CI::get(Int64, lowerExtreme(), isSigned()), + CI::get(Int64, LowerBound - 1, isSigned())); + + assert(false && "The OSR is unlimited"); + } + + bool operator ==(const BoundedValue &Other) const { + if (Bottom || Other.Bottom) + return Bottom == Other.Bottom; + + return (Value == Other.Value + && LowerBound == Other.LowerBound + && UpperBound == Other.UpperBound + && Sign == Other.Sign + && Bottom == Other.Bottom + && Negated == Other.Negated + && Weak == Other.Weak); + } + + bool operator !=(const BoundedValue &Other) { + return !(*this == Other); + } + + void flip() { + Negated = !Negated; + return; + } + + void setBottom() { + assert(!Bottom); + Bottom = true; + } + + bool isBottom() const { return Bottom; } + + bool isTop() const { + return (isUninitialized() + || (!Negated + && LowerBound == lowerExtreme() + && UpperBound == upperExtreme())); + } + + void setWeak() { + Weak = true; + } + + bool isWeak() const { return Weak; } + + uint64_t size() const { + if (!Negated) + return UpperBound - LowerBound; + else if (LowerBound == lowerExtreme()) + return upperExtreme() - (UpperBound + 1); + else if (UpperBound == upperExtreme()) + return (LowerBound - 1) - lowerExtreme(); + + assert(false && "The OSR is unlimited"); + } + + static BoundedValue createGE(const llvm::Value *V, + uint64_t Value, + bool Sign) { + BoundedValue Result(V); + Result.setSignedness(Sign); + Result.LowerBound = Value; + Result.UpperBound = Result.upperExtreme(); + return Result; + } + + static BoundedValue createLE(const llvm::Value *V, + uint64_t Value, + bool Sign) { + BoundedValue Result(V); + Result.setSignedness(Sign); + Result.LowerBound = Result.lowerExtreme(); + Result.UpperBound = Value; + return Result; + } + + static BoundedValue createEQ(const llvm::Value *V, + uint64_t Value, + bool Sign) { + BoundedValue Result(V); + Result.setSignedness(Sign); + Result.LowerBound = Value; + Result.UpperBound = Value; + return Result; + } + + static BoundedValue createNE(const llvm::Value *V, + uint64_t Value, + bool Sign) { + BoundedValue Result(V); + Result.setSignedness(Sign); + Result.LowerBound = Value; + Result.UpperBound = Value; + Result.Negated = true; + return Result; + } + + void setTop() { + if (isUninitialized()) + return; + + LowerBound = lowerExtreme(); + UpperBound = upperExtreme(); + Negated = false; + } + + bool isSingleRange() const { + if (!Negated) + return true; + else + return LowerBound == lowerExtreme() || UpperBound == upperExtreme(); + } + + private: + uint64_t lowerExtreme() const { + switch (Sign) { + case Unsigned: + return std::numeric_limits::min(); + case Signed: + return std::numeric_limits::min(); + case InconsistentSignedness: + return std::numeric_limits::min(); + default: + assert(false); + } + } + + uint64_t upperExtreme() const { + switch (Sign) { + case Unsigned: + return std::numeric_limits::max(); + case Signed: + return std::numeric_limits::max(); + case InconsistentSignedness: + return std::numeric_limits::max(); + default: + assert(false); + } + } + + public: + const llvm::Value *Value; + uint64_t LowerBound; + uint64_t UpperBound; + + enum Signedness : uint8_t { + UnknownSignedness, + Unsigned, + Signed, + InconsistentSignedness + }; + + Signedness Sign; + uint8_t Bottom; + uint8_t Negated; + uint8_t Weak; + }; + + class OSR { + public: + OSR(const BoundedValue *Value) : Base(0), Factor(1), BV(Value) { } + OSR(uint64_t Base) : Base(Base), Factor(0), BV(nullptr) { } + OSR() : Base(0), Factor(1), BV(nullptr) { } + OSR(const OSR &Other) : + Base(Other.Base), + Factor(Other.Factor), + BV(Other.BV) { } + + bool combine(unsigned Opcode, llvm::Constant *Operand, + const llvm::DataLayout &DL); + + llvm::Constant *solveEquation(llvm::Constant *KnownTerm, + bool CeilingRounding, + const llvm::DataLayout &DL); + + bool isRelativeTo(const llvm::Value *V) const { + return !isConstant() && BV->value() == V; + } + + bool isWeak() const { return !isConstant() && BV->isWeak(); } + + void setBoundedValue(BoundedValue *NewBV) { + BV = NewBV; + } + + const BoundedValue *boundedValue() const { + assert(BV != nullptr); + return BV; + } + + bool operator ==(const OSR& Other) const { + return Base == Other.Base && Factor == Other.Factor && BV == Other.BV; + } + + bool operator !=(const OSR& Other) const { + return !(*this == Other); + } + + void describe(llvm::formatted_raw_ostream &O) const; + + bool isConstant() const { + return !(BV != nullptr && BV->isBottom()) && Factor == 0; + } + + bool compare(unsigned short P, + llvm::Constant *C, + const llvm::DataLayout &DL, + llvm::Type *Int64); + + llvm::Constant *evaluate(llvm::Constant *Value, + llvm::Type *Int64) const; + + uint64_t absFactor(llvm::Type *Int64, + const llvm::DataLayout &DL) const; + + std::pair boundaries(llvm::Type *Int64, + const llvm::DataLayout &DL) const; + + uint64_t size() const { return BV->size(); } + + uint64_t base() const { return Base; } + uint64_t factor() const { return Factor; } + + private: + uint64_t Base; + uint64_t Factor; + const BoundedValue *BV; + }; + +private: + class BVMap { + private: + using MapIndex = std::pair; + using BVWithOrigin = std::pair; + struct MapValue { + BoundedValue Summary; + std::vector Components; + }; + + public: + BVMap() : BlockBlackList(nullptr), DL(nullptr), Int64(nullptr) { } + BVMap(std::set *BlackList, + const llvm::DataLayout *DL, + llvm::Type *Int64) : + BlockBlackList(BlackList), DL(DL), Int64(Int64) { } + + void describe(llvm::formatted_raw_ostream &O, + const llvm::BasicBlock *BB) const; + + BoundedValue &get(llvm::BasicBlock *BB, const llvm::Value *V) { + auto Index = std::make_pair(BB, V); + auto MapIt = TheMap.find(Index); + if (MapIt == TheMap.end()) { + MapValue NewBVOVector; + NewBVOVector.Summary = BoundedValue(V); + auto It = TheMap.insert(std::make_pair(Index, NewBVOVector)).first; + return summarize(BB, &It->second); + } + + MapValue &BVOs = MapIt->second; + return BVOs.Summary; + } + + BoundedValue &getWeak(llvm::BasicBlock *BB, const llvm::Value *V) { + auto Index = std::make_pair(BB, V); + auto MapIt = TheMap.find(Index); + if (MapIt == TheMap.end()) { + MapValue NewBVOVector; + BoundedValue BV(V); + BV.setWeak(); + NewBVOVector.Summary = BV; + auto It = TheMap.insert(std::make_pair(Index, NewBVOVector)).first; + return summarize(BB, &It->second); + } + + MapValue &BVOs = MapIt->second; + assert(BVOs.Summary.isWeak()); + for (auto &BV : BVOs.Components) + assert(BV.second.isWeak()); + return BVOs.Summary; + } + + void setSignedness(llvm::BasicBlock *BB, + const llvm::Value *V, + bool IsSigned) { + auto Index = std::make_pair(BB, V); + auto MapIt = TheMap.find(Index); + assert(MapIt != TheMap.end()); + + MapValue &BVOVector = MapIt->second; + BVOVector.Summary.setSignedness(IsSigned); + for (BVWithOrigin &BVO : BVOVector.Components) + BVO.second.setSignedness(IsSigned); + + summarize(BB, &MapIt->second); + } + + std::pair update(llvm::BasicBlock *Target, + llvm::BasicBlock *Origin, + BoundedValue NewBV); + + void prepareDescribe() const { + BBMap.clear(); + for (auto Pair : TheMap) { + auto *BB = Pair.first.first; + if (BBMap.find(BB) == BBMap.end()) + BBMap[BB] = std::vector { Pair.second }; + else + BBMap[BB].push_back(Pair.second); + } + } + + private: + BoundedValue &summarize(llvm::BasicBlock *Target, + MapValue *BVOVectorLoopInfoWrapperPass); + private: + std::set *BlockBlackList; + const llvm::DataLayout *DL; + llvm::Type *Int64; + std::map TheMap; + mutable std::map> BBMap; + }; + +public: + const OSR *getOSR(const llvm::Value *V) { + auto *I = llvm::dyn_cast(V); + + if (I == nullptr) + return nullptr; + + auto It = OSRs.find(I); + if (It == OSRs.end()) + return nullptr; + else + return &It->second; + } + + std::pair + identifyOperands(const llvm::Instruction *I, + llvm::Type *Int64, + const llvm::DataLayout &DL); + +private: + OSR switchBlock(OSR Base, llvm::BasicBlock *BB) { + if (!Base.isConstant()) + Base.setBoundedValue(&BVs.get(BB, Base.boundedValue()->value())); + return Base; + } + + /// Returns a copy of the OSR associated with the given value, or if it does + /// not exist, create a new one. In both cases the return value will refer to + /// a bounded value in the context of the given basic block. + /// Note: after invoking this function you should always check if the result + /// is not expressed in terms of the instruction you're analyzing + /// itself, otherwise we could create (possibly infinite) loops we're + /// not really interested in. + OSR createOSR(llvm::Value *V, llvm::BasicBlock *BB); + +private: + // TODO: why value and not instruction? + std::map OSRs; + BVMap BVs; + using BVVector = llvm::SmallVector; + std::map Constraints; +}; + +#endif // _OSRA_H