diff --git a/include/revng/StackAnalysis/FunctionsSummary.h b/include/revng/StackAnalysis/FunctionsSummary.h deleted file mode 100644 index 08356fbb3..000000000 --- a/include/revng/StackAnalysis/FunctionsSummary.h +++ /dev/null @@ -1,782 +0,0 @@ -#pragma once - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include -#include -#include - -#include "revng/Support/Debug.h" -#include "revng/Support/MetaAddress.h" - -namespace llvm { -class BasicBlock; -class GlobalVariable; -class Instruction; -class Module; -class Value; -} // namespace llvm - -namespace StackAnalysis { - -namespace FunctionType { - -enum Values { - Invalid, ///< An invalid entry - Regular, ///< A normal function - NoReturn, ///< A noreturn function - Fake ///< A fake function -}; - -inline const char *getName(Values Type) { - switch (Type) { - case Invalid: - return "Invalid"; - case Regular: - return "Regular"; - case NoReturn: - return "NoReturn"; - case Fake: - return "Fake"; - } - - revng_abort(); -} - -inline Values fromName(llvm::StringRef Name) { - if (Name == "Invalid") - return Invalid; - else if (Name == "Regular") - return Regular; - else if (Name == "NoReturn") - return NoReturn; - else if (Name == "Fake") - return Fake; - else - revng_abort(); -} - -} // namespace FunctionType - -/// \brief Intraprocedural analysis interruption reasons -namespace BranchType { - -enum Values { - /// Invalid value - Invalid, - /// Branch due to instruction-level CFG (e.g., conditional move) - InstructionLocalCFG, - /// Branch due to function-local CFG (a regular branch) - FunctionLocalCFG, - /// A call to a fake function - FakeFunctionCall, - /// A return from a fake function - FakeFunctionReturn, - /// A function call for which the cache was able to produce a summary - HandledCall, - /// A function call for which the target is unknown - IndirectCall, - /// A function call for which the cache was not able to produce a summary - UnhandledCall, - /// A proper function return - Return, - /// A branch returning to the return address, but leaving the stack - /// in an unexpected situation - BrokenReturn, - /// A branch representing an indirect tail call - IndirectTailCall, - /// A branch representing a longjmp or similar constructs - LongJmp, - /// A killer basic block (killer syscall or endless loop) - Killer, - /// The basic block ends with an unreachable instruction - Unreachable, - - /// This function is fake, inform the interprocedural part of the analysis - FakeFunction, - /// The analysis of the function is finished and a summary is available - RegularFunction, - /// This is a function for which we couldn't find any return statement - NoReturnFunction, -}; - -inline const char *getName(Values Type) { - switch (Type) { - case Invalid: - return "Invalid"; - case InstructionLocalCFG: - return "InstructionLocalCFG"; - case FunctionLocalCFG: - return "FunctionLocalCFG"; - case FakeFunctionCall: - return "FakeFunctionCall"; - case FakeFunctionReturn: - return "FakeFunctionReturn"; - case HandledCall: - return "HandledCall"; - case IndirectCall: - return "IndirectCall"; - case UnhandledCall: - return "UnhandledCall"; - case Return: - return "Return"; - case BrokenReturn: - return "BrokenReturn"; - case IndirectTailCall: - return "IndirectTailCall"; - case FakeFunction: - return "FakeFunction"; - case LongJmp: - return "LongJmp"; - case Killer: - return "Killer"; - case RegularFunction: - return "RegularFunction"; - case NoReturnFunction: - return "NoReturnFunction"; - case Unreachable: - return "Unreachable"; - } - - revng_abort(); -} - -inline Values fromName(llvm::StringRef Name) { - if (Name == "Invalid") - return Invalid; - else if (Name == "InstructionLocalCFG") - return InstructionLocalCFG; - else if (Name == "FunctionLocalCFG") - return FunctionLocalCFG; - else if (Name == "FakeFunctionCall") - return FakeFunctionCall; - else if (Name == "FakeFunctionReturn") - return FakeFunctionReturn; - else if (Name == "HandledCall") - return HandledCall; - else if (Name == "IndirectCall") - return IndirectCall; - else if (Name == "UnhandledCall") - return UnhandledCall; - else if (Name == "Return") - return Return; - else if (Name == "BrokenReturn") - return BrokenReturn; - else if (Name == "IndirectTailCall") - return IndirectTailCall; - else if (Name == "FakeFunction") - return FakeFunction; - else if (Name == "LongJmp") - return LongJmp; - else if (Name == "Killer") - return Killer; - else if (Name == "RegularFunction") - return RegularFunction; - else if (Name == "NoReturnFunction") - return NoReturnFunction; - else if (Name == "Unreachable") - return Unreachable; - else - revng_abort(); -} - -} // namespace BranchType - -/// \brief Class representing the state of a register in terms of being an -/// argument of a function or a function call -/// -/// Collects information from URAOF, DRAOF and RAOFC. -/// -/// \tparam FunctionCall true if this class represents the state of a register -/// in terms of being an argument of a function call (as opposed to a -/// function). -template -class RegisterArgument { - // We're friends with the class with opposite FunctionCall status - friend class RegisterArgument; - - friend struct CombineHelper; - -public: - enum Values { No, NoOrDead, Dead, Yes, Maybe, Contradiction }; - -private: - Values Value; - -public: - RegisterArgument() : Value(Maybe) {} - - RegisterArgument(Values Value) : Value(Value) {} - - static RegisterArgument no() { - RegisterArgument Result; - Result.Value = No; - return Result; - } - - static RegisterArgument maybe() { - RegisterArgument Result; - Result.Value = Maybe; - return Result; - } - - static RegisterArgument fromName(llvm::StringRef Name) { - RegisterArgument Result; - if (Name == "No") - Result.Value = No; - else if (Name == "NoOrDead") - Result.Value = NoOrDead; - else if (Name == "Dead") - Result.Value = Dead; - else if (Name == "Yes") - Result.Value = Yes; - else if (Name == "Maybe") - Result.Value = Maybe; - else if (Name == "Contradiction") - Result.Value = Contradiction; - else - revng_abort(); - - return Result; - } - -public: - bool isContradiction() const { return Value == Contradiction; } - - /// This RegisterArgument concerns a function call for which either the callee - /// is unknown or doesn't use this register at all. - void notAvailable() { - revng_assert(FunctionCall); - - // All the situations which embed the possibility of being an argument have - // to go to a case that includes the possibility of *not* being an argument - // due to the register being a callee-saved register - switch (Value) { - case Maybe: - // These are already good - break; - case Yes: - // Weaken the statement - Value = Maybe; - break; - case No: - case NoOrDead: - case Contradiction: - case Dead: - revng_abort(); - } - } - - void combine(const RegisterArgument &Other); - - bool isCompatibleWith(const RegisterArgument &Other) const { - switch (Value) { - case NoOrDead: - switch (Other.Value) { - case RegisterArgument::NoOrDead: - case RegisterArgument::Dead: - case RegisterArgument::No: - case RegisterArgument::Maybe: - return true; - case RegisterArgument::Yes: - case RegisterArgument::Contradiction: - return false; - } - break; - case Maybe: - switch (Other.Value) { - case RegisterArgument::Maybe: - case RegisterArgument::NoOrDead: - case RegisterArgument::Dead: - case RegisterArgument::No: - case RegisterArgument::Yes: - return true; - case RegisterArgument::Contradiction: - return false; - } - break; - case Yes: - switch (Other.Value) { - case RegisterArgument::Yes: - case RegisterArgument::Maybe: - return true; - case RegisterArgument::NoOrDead: - case RegisterArgument::Dead: - case RegisterArgument::No: - case RegisterArgument::Contradiction: - return false; - } - break; - case Dead: - switch (Other.Value) { - case RegisterArgument::Dead: - case RegisterArgument::NoOrDead: - case RegisterArgument::Maybe: - return true; - case RegisterArgument::Yes: - case RegisterArgument::No: - case RegisterArgument::Contradiction: - return false; - } - break; - case Contradiction: - return false; - case No: - switch (Other.Value) { - case RegisterArgument::No: - case RegisterArgument::NoOrDead: - case RegisterArgument::Maybe: - return true; - case RegisterArgument::Dead: - case RegisterArgument::Yes: - case RegisterArgument::Contradiction: - return false; - } - break; - } - - revng_abort(); - } - - const char *valueName() const { - switch (Value) { - case NoOrDead: - return "NoOrDead"; - case Maybe: - return "Maybe"; - case Yes: - return "Yes"; - case Dead: - return "Dead"; - case Contradiction: - return "Contradiction"; - case No: - return "No"; - } - - revng_abort(); - } - - Values value() const { return Value; } - - void dump() const { dump(dbg); } - - template - void dump(T &Output) const { - Output << valueName(); - } -}; - -// Assign a name to the two possible options -using FunctionRegisterArgument = RegisterArgument; -using FunctionCallRegisterArgument = RegisterArgument; - -template<> -void RegisterArgument::combine(const RegisterArgument &Other); -template<> -void RegisterArgument::combine(const RegisterArgument &Other); - -// Let includers know that someone will define the two classes -extern template class RegisterArgument; -extern template class RegisterArgument; - -class FunctionCallReturnValue; - -/// \brief Class representing the state of a register in terms of being the -/// return value of a function. -/// -/// Collects information from URVOF. -class FunctionReturnValue { - friend class FunctionCallReturnValue; - - friend struct CombineHelper; - -public: - enum Values { No, NoOrDead, Maybe, Contradiction, YesOrDead }; - -private: - Values Value; - -public: - FunctionReturnValue() : Value(Maybe) {} - - FunctionReturnValue(Values Value) : Value(Value) {} - - static FunctionReturnValue no() { - FunctionReturnValue Result; - Result.Value = No; - return Result; - } - - static FunctionReturnValue maybe() { - FunctionReturnValue Result; - Result.Value = Maybe; - return Result; - } - - static FunctionReturnValue fromName(llvm::StringRef Name) { - FunctionReturnValue Result; - if (Name == "No") - Result.Value = No; - else if (Name == "NoOrDead") - Result.Value = NoOrDead; - else if (Name == "Maybe") - Result.Value = Maybe; - else if (Name == "Contradiction") - Result.Value = Contradiction; - else if (Name == "YesOrDead") - Result.Value = YesOrDead; - else - revng_abort(); - return Result; - } - -public: - bool isContradiction() const { return Value == Contradiction; } - - void notAvailable() { revng_abort(); } - - void combine(const FunctionCallReturnValue &Other); - - const char *valueName() const { - switch (Value) { - case NoOrDead: - return "NoOrDead"; - case Maybe: - return "Maybe"; - case No: - return "No"; - case Contradiction: - return "Contradiction"; - case YesOrDead: - return "YesOrDead"; - } - - revng_abort(); - } - - Values value() const { return Value; } - - void dump() const { dump(dbg); } - - template - void dump(T &Output) const { - Output << valueName(); - } -}; - -/// \brief Class representing the state of a register in terms of being the -/// return value of a function call. -/// -/// Collects information from DRVOFC and URVOFC. -class FunctionCallReturnValue { - friend class FunctionReturnValue; - - friend struct CombineHelper; - -public: - enum Values { No, NoOrDead, Maybe, Yes, Dead, Contradiction, YesOrDead }; - -private: - Values Value; - -public: - FunctionCallReturnValue() : Value(Maybe) {} - - FunctionCallReturnValue(Values Value) : Value(Value) {} - - static FunctionCallReturnValue no() { - FunctionCallReturnValue Result; - Result.Value = No; - return Result; - } - - static FunctionCallReturnValue maybe() { - FunctionCallReturnValue Result; - Result.Value = Maybe; - return Result; - } - - static FunctionCallReturnValue fromName(llvm::StringRef Name) { - FunctionCallReturnValue Result; - - if (Name == "NoOrDead") - Result.Value = NoOrDead; - else if (Name == "Maybe") - Result.Value = Maybe; - else if (Name == "Yes") - Result.Value = Yes; - else if (Name == "Dead") - Result.Value = Dead; - else if (Name == "Contradiction") - Result.Value = Contradiction; - else if (Name == "No") - Result.Value = No; - else if (Name == "YesOrDead") - Result.Value = YesOrDead; - else - revng_abort(); - - return Result; - } - -public: - bool isContradiction() const { return Value == Contradiction; } - - void notAvailable() { - // All the situations which embed the possibility of being an argument have - // to go to a case that includes the possibility of *not* being an argument - // due to the register being a callee-saved register - switch (Value) { - case NoOrDead: - case Maybe: - // These are fine - break; - - case Yes: - // Weaken Yes statement - Value = Maybe; - break; - - case No: - case Contradiction: - case YesOrDead: - case Dead: - revng_abort(); - } - } - - void combine(const FunctionReturnValue &Other); - - bool isCompatibleWith(const FunctionReturnValue &Other) const { - switch (Value) { - case YesOrDead: - switch (Other.Value) { - case FunctionReturnValue::YesOrDead: - case FunctionReturnValue::Maybe: - case FunctionReturnValue::NoOrDead: - return true; - case FunctionReturnValue::No: - case FunctionReturnValue::Contradiction: - return false; - } - break; - case NoOrDead: - switch (Other.Value) { - case FunctionReturnValue::NoOrDead: - case FunctionReturnValue::No: - case FunctionReturnValue::Maybe: - case FunctionReturnValue::YesOrDead: - return true; - case FunctionReturnValue::Contradiction: - return false; - } - break; - case Maybe: - switch (Other.Value) { - case FunctionReturnValue::Maybe: - case FunctionReturnValue::YesOrDead: - case FunctionReturnValue::NoOrDead: - case FunctionReturnValue::No: - return true; - case FunctionReturnValue::Contradiction: - return false; - } - break; - case Yes: - switch (Other.Value) { - case FunctionReturnValue::YesOrDead: - case FunctionReturnValue::Maybe: - return true; - case FunctionReturnValue::NoOrDead: - case FunctionReturnValue::No: - case FunctionReturnValue::Contradiction: - return false; - } - break; - case Dead: - switch (Other.Value) { - case FunctionReturnValue::NoOrDead: - case FunctionReturnValue::YesOrDead: - case FunctionReturnValue::Maybe: - return true; - case FunctionReturnValue::No: - case FunctionReturnValue::Contradiction: - return false; - } - break; - case Contradiction: - return false; - case No: - switch (Other.Value) { - case FunctionReturnValue::NoOrDead: - case FunctionReturnValue::No: - case FunctionReturnValue::Maybe: - return true; - case FunctionReturnValue::YesOrDead: - case FunctionReturnValue::Contradiction: - return false; - } - break; - } - - revng_abort(); - } - - const char *valueName() const { - switch (Value) { - case NoOrDead: - return "NoOrDead"; - case Maybe: - return "Maybe"; - case Yes: - return "Yes"; - case Dead: - return "Dead"; - case Contradiction: - return "Contradiction"; - case YesOrDead: - return "YesOrDead"; - case No: - return "No"; - } - - revng_abort(); - } - - Values value() const { return Value; } - - void dump() const { dump(dbg); } - - template - void dump(T &Output) const { - Output << valueName(); - } -}; - -template -inline V getOrDefault(const std::map &Map, K Key) { - auto It = Map.find(Key); - if (It == Map.end()) - return V(); - else - return It->second; -} - -/// \brief Class containg the final results about all the analyzed functions -class FunctionsSummary { -public: - struct FunctionRegisterDescription { - FunctionRegisterArgument Argument; - FunctionReturnValue ReturnValue; - }; - - struct FunctionCallRegisterDescription { - FunctionCallRegisterArgument Argument; - FunctionCallReturnValue ReturnValue; - - bool isCompatibleWith(const FunctionRegisterDescription &FRD) const { - return (Argument.isCompatibleWith(FRD.Argument) - and ReturnValue.isCompatibleWith(FRD.ReturnValue)); - } - }; - - struct FunctionDescription; - - // TODO: this is finalized stuff, should we use vectors/SmalMaps instead of - // maps? - struct CallSiteDescription { - CallSiteDescription(llvm::Instruction *Call, llvm::Value *Callee) : - Call(Call), Callee(Callee) {} - - llvm::Instruction *Call; - llvm::Value *Callee; - - using GlobalVariable = llvm::GlobalVariable; - - template - using map = std::map; - - map RegisterSlots; - - llvm::GlobalVariable * - isCompatibleWith(const FunctionDescription &Function) const; - }; - - struct FunctionDescription { - FunctionDescription() : Function(nullptr), Type(FunctionType::Invalid) {} - - llvm::Value *Function; - FunctionType::Values Type; - std::map BasicBlocks; - // TODO: this should be a vector - std::map RegisterSlots; - std::deque CallSites; - std::set ClobberedRegisters; - std::multimap FakeReturns; - }; - -public: - /// \brief Map from function entry points to its description - std::map Functions; - -public: - void dump(const llvm::Module *M) const debug_function { dump(M, dbg); } - - /// \brief Dump in JSON format - /// - /// [ - /// { - /// "entry_point": "bb.main", - /// "entry_point_address": "0x1234", - /// "type": "type", - /// "reasons": ["Callee", "Direct", ...], - /// "basic_blocks": [ - /// { - /// "name": "...", - /// "type": "...", - /// "start": "0x4444", - /// "end": "0x4488" - /// }, - /// ... - /// ], - /// "slots": [ - /// { - /// "slot": "CPU+rax", - /// "argument": "Dead", - /// "return_value": "Dead" - /// }, - /// ... - /// ], - /// "range": [{"start": "0x5555", "end": "0x6666"}, ...], - /// "function_calls": [ - /// { - /// "caller": "bb.callee:12", - /// "caller_address": "0x4567", - /// "slots": [ - /// { - /// "slot": "CPU+rax", - /// "argument": "Dead", - /// "return_value": "Dead" - /// }, - /// ... - /// ], - /// } - /// ], - /// } - /// ] - /// - /// \note The functions are sorted according to entry_point. reasons are - /// are sorted too. - template - void dump(const llvm::Module *M, O &Output) const { - dumpInternal(M, StreamWrapper(Output)); - } - -private: - void dumpInternal(const llvm::Module *M, StreamWrapperBase &&Stream) const; -}; - -} // namespace StackAnalysis diff --git a/include/revng/StackAnalysis/StackAnalysis.h b/include/revng/StackAnalysis/StackAnalysis.h index e07ea3766..a5005c08d 100644 --- a/include/revng/StackAnalysis/StackAnalysis.h +++ b/include/revng/StackAnalysis/StackAnalysis.h @@ -15,14 +15,12 @@ #include "llvm/Support/DOTGraphTraits.h" #include "llvm/Support/GraphWriter.h" -#include "revng/ABIAnalyses/ABIAnalysis.h" #include "revng/ADT/GenericGraph.h" #include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h" #include "revng/FunctionCallIdentification/FunctionCallIdentification.h" #include "revng/Model/Binary.h" #include "revng/Model/LoadModelPass.h" #include "revng/StackAnalysis/AAWriterPass.h" -#include "revng/StackAnalysis/FunctionsSummary.h" #include "revng/StackAnalysis/IndirectBranchInfoPrinterPass.h" #include "revng/StackAnalysis/PromoteGlobalToLocalVars.h" #include "revng/StackAnalysis/SegregateDirectStackAccesses.h" @@ -32,11 +30,7 @@ namespace StackAnalysis { -extern const std::set EmptyCSVSet; - class StackAnalysis : public llvm::ModulePass { - friend class FunctionBoundariesDetectionPass; - public: static char ID; @@ -50,23 +44,6 @@ public: } bool runOnModule(llvm::Module &M) override; - - const std::set & - getClobbered(llvm::BasicBlock *Function) const { - auto It = GrandResult.Functions.find(Function); - if (It == GrandResult.Functions.end()) - return EmptyCSVSet; - else - return It->second.ClobberedRegisters; - } - - void serialize(std::ostream &Output) { Output << TextRepresentation; } - - void serializeMetadata(llvm::Function &F, GeneratedCodeBasicInfo &GCBI); - -public: - FunctionsSummary GrandResult; - std::string TextRepresentation; }; } // namespace StackAnalysis diff --git a/lib/FunctionIsolation/EnforceABI.cpp b/lib/FunctionIsolation/EnforceABI.cpp index 7cbe83076..b29781614 100644 --- a/lib/FunctionIsolation/EnforceABI.cpp +++ b/lib/FunctionIsolation/EnforceABI.cpp @@ -31,19 +31,6 @@ using namespace llvm; -using StackAnalysis::FunctionCallRegisterArgument; -using StackAnalysis::FunctionCallReturnValue; -using StackAnalysis::FunctionRegisterArgument; -using StackAnalysis::FunctionReturnValue; -using StackAnalysis::FunctionsSummary; - -using CallSiteDescription = FunctionsSummary::CallSiteDescription; -using FunctionDescription = FunctionsSummary::FunctionDescription; -using FCRD = FunctionsSummary::FunctionCallRegisterDescription; -using FunctionCallRegisterDescription = FCRD; -using FRD = FunctionsSummary::FunctionRegisterDescription; -using FunctionRegisterDescription = FRD; - char EnforceABI::ID = 0; using Register = RegisterPass; static Register X("enforce-abi", "Enforce ABI Pass", true, true); diff --git a/lib/FunctionIsolation/IsolateFunctions.cpp b/lib/FunctionIsolation/IsolateFunctions.cpp index c4a4796ae..85863019f 100644 --- a/lib/FunctionIsolation/IsolateFunctions.cpp +++ b/lib/FunctionIsolation/IsolateFunctions.cpp @@ -1,6 +1,6 @@ /// \file IsolateFunctions.cpp /// \brief Implements the IsolateFunctions pass which applies function isolation -/// using the informations provided by FunctionBoundariesDetectionPass. +/// using the informations provided by StackAnalysis. // // This file is distributed under the MIT License. See LICENSE.md for details. diff --git a/lib/StackAnalysis/ABIDataFlows-header.inc b/lib/StackAnalysis/ABIDataFlows-header.inc deleted file mode 100644 index 4ddd5a7c4..000000000 --- a/lib/StackAnalysis/ABIDataFlows-header.inc +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -// This file has been automatically generated, please don't change it - -#include -#include - -#include "revng/Support/Assert.h" -#include "revng/Support/Debug.h" diff --git a/lib/StackAnalysis/ABIDetectionPass.cpp b/lib/StackAnalysis/ABIDetectionPass.cpp index 41f96bbb8..dcfcd988a 100644 --- a/lib/StackAnalysis/ABIDetectionPass.cpp +++ b/lib/StackAnalysis/ABIDetectionPass.cpp @@ -1,12 +1,9 @@ /// \file ABIDetectionPass.cpp -/// \brief // // This file is distributed under the MIT License. See LICENSE.md for details. // -#include - #include "llvm/Support/CommandLine.h" #include "revng/StackAnalysis/ABIDetectionPass.h" @@ -14,51 +11,6 @@ using namespace llvm; using namespace llvm::cl; -static opt FBDPOutputPath("detect-function-boundaries-output", - desc("Destination path for the Function " - "Boundaries Detection Pass"), - value_desc("path"), - cat(MainCategory)); - -template -struct CompareByName { - bool operator()(const T *LHS, const T *RHS) const { - return LHS->getName() < RHS->getName(); - } -}; - -static void serializeFunctionBoundaries(std::ostream &Output, Module &M) { - using namespace llvm; - - QuickMetadata QMD(getContext(&M)); - Function &F = *M.getFunction("root"); - std::map> Functions; - - for (BasicBlock &BB : F) { - if (!BB.empty()) { - Instruction *Terminator = BB.getTerminator(); - if (MDNode *Node = Terminator->getMetadata("revng.func.member.of")) { - auto *Tuple = cast(Node); - for (const MDOperand &Op : Tuple->operands()) { - auto *FunctionMD = cast(Op); - auto *FirstOperand = QMD.extract(FunctionMD, 0); - auto *FunctionNameMD = QMD.extract(FirstOperand, 0); - Functions[FunctionNameMD->getString()].push_back(&BB); - } - } - } - } - - Output << "function,basicblock\n"; - - auto Comparator = CompareByName(); - for (auto &P : Functions) { - std::sort(P.second.begin(), P.second.end(), Comparator); - for (BasicBlock *BB : P.second) - Output << P.first.data() << "," << BB->getName().data() << "\n"; - } -} - namespace StackAnalysis { char ABIDetectionPass::ID = 0; @@ -68,12 +20,6 @@ static Register X("detect-abi", "ABI Detection Pass", true, true); bool ABIDetectionPass::runOnModule(Module &M) { auto &GCBI = getAnalysis().getGCBI(); auto &SA = getAnalysis(); - SA.serializeMetadata(*M.getFunction("root"), GCBI); - - if (FBDPOutputPath.getNumOccurrences() == 1) { - std::ofstream Output; - serializeFunctionBoundaries(pathToStream(FBDPOutputPath, Output), M); - } return false; } diff --git a/lib/StackAnalysis/ABIIR.cpp b/lib/StackAnalysis/ABIIR.cpp deleted file mode 100644 index 5b312c485..000000000 --- a/lib/StackAnalysis/ABIIR.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/// \file ABIIR.cpp -/// \brief - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include "llvm/ADT/GraphTraits.h" -#include "llvm/ADT/PostOrderIterator.h" - -#include "ABIIR.h" - -namespace StackAnalysis { - -void ABIFunction::finalize() { - revng_assert(Calls.empty()); - - for (auto &P : BBMap) { - // Build backward links - for (ABIIRBasicBlock *Successor : P.second.Successors) - Successor->Predecessors.push_back(&P.second); - - if (P.second.successor_size() == 0) - FinalBBs.push_back(&P.second); - - // Find all the function calls - for (ABIIRInstruction &I : P.second) - if (I.isCall()) - Calls.emplace_back(&P.second, &I); - } - - // The entry point should not have predecessors - if (IREntry->predecessor_size() != 0) { - ABIIRBasicBlock *NewEntry = &this->get(nullptr); - NewEntry->addSuccessor(IREntry); - IREntry->Predecessors.push_back(NewEntry); - IREntry = NewEntry; - } - - // Prune - { - OnceQueue ToVisit; - ToVisit.insert(IREntry); - - while (not ToVisit.empty()) { - ABIIRBasicBlock *Block = ToVisit.pop(); - for (ABIIRBasicBlock *Successor : Block->successors()) { - ToVisit.insert(Successor); - } - } - - std::set Visited = ToVisit.visited(); - { - auto IsUnreachable = [&Visited](ABIIRBasicBlock *Block) { - return Visited.count(Block) == 0; - }; - std::erase_if(FinalBBs, IsUnreachable); - } - { - auto IsUnreachable = [&Visited](decltype(BBMap)::value_type &Block) { - return Visited.count(&Block.second) == 0; - }; - std::erase_if(BBMap, IsUnreachable); - } - } -} - -bool ABIFunction::verify() const { - for (auto &P : BBMap) { - const ABIIRBasicBlock &BB = P.second; - - for (auto &Successor : BB.successors()) { - auto SuccessorPredecessors = Successor->predecessors(); - auto StartIt = SuccessorPredecessors.begin(); - auto EndIt = SuccessorPredecessors.end(); - if (std::find(StartIt, EndIt, &BB) == EndIt) - return false; - } - - for (auto &Predecessor : BB.predecessors()) { - auto PredecessorSuccessors = Predecessor->successors(); - auto StartIt = PredecessorSuccessors.begin(); - auto EndIt = PredecessorSuccessors.end(); - if (std::find(StartIt, EndIt, &BB) == EndIt) - return false; - } - - if (BB.predecessor_size() == 0 and &BB != IREntry) - return false; - } - - return true; -} - -std::set ABIFunction::writtenRegisters() const { - std::set WrittenRegisters; - - for (const auto &P : BBMap) - for (const ABIIRInstruction &I : P.second) - if (I.isStore() and I.target().addressSpace() == ASID::cpuID()) - WrittenRegisters.insert(I.target().offset()); - - return WrittenRegisters; -} - -std::set ABIFunction::incoherentCalls() { - std::vector Extremals; - for (auto &P : BBMap) - if (P.second.successor_size() == 0) - Extremals.push_back(&P.second); - - return computeIncoherentCalls(entry(), Extremals); -} - -void ABIFunction::dumpDot() const { - std::map RPOTPosition; - { - llvm::ReversePostOrderTraversal RPOT(IREntry); - unsigned I = 0; - for (ABIIRBasicBlock *BB : RPOT) - RPOTPosition[BB] = I++; - } - - dbg << "digraph ABIFunction {\n"; - - for (auto &P : BBMap) { - const ABIIRBasicBlock &BB = P.second; - dbg << "\"" << getName(BB.basicBlock()) << "\" ["; - dbg << "label=\"" << getName(BB.basicBlock()) << " "; - - auto It = RPOTPosition.find(&BB); - if (It == RPOTPosition.end()) - dbg << "N/A"; - else - dbg << It->second; - - dbg << "\""; - if (&BB == IREntry) - dbg << "fillcolor=green,style=filled"; - dbg << "];\n"; - - for (auto &Successor : BB.successors()) { - dbg << "\"" << getName(BB.basicBlock()) << "\"" - << " -> \"" << getName(Successor->basicBlock()) << "\"" - << " [color=green];\n"; - } - } - - dbg << "}\n"; -} - -} // namespace StackAnalysis diff --git a/lib/StackAnalysis/ABIIR.h b/lib/StackAnalysis/ABIIR.h deleted file mode 100644 index 6291268a0..000000000 --- a/lib/StackAnalysis/ABIIR.h +++ /dev/null @@ -1,489 +0,0 @@ -#pragma once - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include - -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringRef.h" - -#include "revng/ADT/Queue.h" - -#include "ASSlot.h" -#include "FunctionABI.h" - -namespace StackAnalysis { - -/// \brief Instruction of the ABI IR -class ABIIRInstruction { -public: - enum Opcode { Load, Store, DirectCall, IndirectCall }; - -private: - /// Instruction opcode - Opcode O; - - /// Load/store target address - const ASSlot Target; - - // - // Call-only fields - // - - /// Reference to the function call - FunctionCall Call; - - /// Result of the ABI analysis for the callee - /// - /// \note FunctionABI can be quite large, create an instance only if needed. - std::unique_ptr ABI; - - /// Set of caller stack slots written by the callee - std::set WrittenStackSlots; - -private: - ABIIRInstruction(Opcode O, - FunctionCall Call, - FunctionABI ABI, - std::set WrittenStackSlots) : - O(O), - Target(ASSlot::invalid()), - Call(Call), - ABI(new FunctionABI(std::move(ABI))), - WrittenStackSlots(std::move(WrittenStackSlots)) { - revng_assert(O == DirectCall); - } - - ABIIRInstruction(Opcode O, ASSlot Target) : - O(O), Target(Target), ABI(), WrittenStackSlots() { - revng_assert(O == Load || O == Store); - } - - ABIIRInstruction(Opcode O, FunctionCall Call) : - O(O), Target(ASSlot::invalid()), Call(Call), ABI(), WrittenStackSlots() { - revng_assert(O == IndirectCall); - } - -public: - static ABIIRInstruction createLoad(const ASSlot Target) { - return ABIIRInstruction(Load, Target); - } - - static ABIIRInstruction createStore(const ASSlot Target) { - return ABIIRInstruction(Store, Target); - } - - static ABIIRInstruction - createDirectCall(FunctionCall Call, - FunctionABI ABI, - std::set WrittenStackSlots) { - return ABIIRInstruction(DirectCall, - Call, - std::move(ABI), - std::move(WrittenStackSlots)); - } - - static ABIIRInstruction createIndirectCall(FunctionCall Call) { - return ABIIRInstruction(IndirectCall, Call); - } - -public: - Opcode opcode() const { return O; } - - bool isCall() const { return O == DirectCall or O == IndirectCall; } - - bool isStore() const { return O == Store; } - - const ASSlot target() const { - revng_assert(O == Load || O == Store); - return Target; - } - - const FunctionABI &abi() const { - revng_assert(O == DirectCall); - return *ABI; - } - - const std::set &stackArguments() const { - revng_assert(O == DirectCall); - return WrittenStackSlots; - } - - FunctionCall call() const { - revng_assert(isCall()); - revng_assert(Call.callInstruction() != nullptr); - return Call; - } - - void dump(const llvm::Module *M) const debug_function { dump(dbg, M); } - - template - void dump(T &Output, const llvm::Module *M) const { - switch (O) { - case Load: - Output << "Load from "; - target().dump(M, Output); - break; - case Store: - Output << "Store to "; - target().dump(M, Output); - break; - case DirectCall: - Output << "DirectCall to " << getName(call().callee()); - Output << " from " << getName(call().callInstruction()); - break; - case IndirectCall: - Output << "IndirectCall from " << getName(call().callInstruction()); - break; - } - } -}; - -/// \brief Basic block of the ABI IR, a container of ABIIRInstructions -class ABIIRBasicBlock { - // The ABIFunction class is our friend so it can finalize us - friend class ABIFunction; - -public: - using links_container = llvm::SmallVector; - using links_iterator = typename links_container::iterator; - using links_const_iterator = typename links_container::const_iterator; - using links_range = llvm::iterator_range; - using links_const_range = llvm::iterator_range; - - using container = std::vector; - - using iterator = typename container::iterator; - using const_iterator = typename container::const_iterator; - - using reverse_iterator = typename container::reverse_iterator; - using const_reverse_iterator = typename container::const_reverse_iterator; - - using range = llvm::iterator_range; - using const_range = llvm::iterator_range; - - using reverse_range = llvm::iterator_range; - using const_reverse_range = llvm::iterator_range; - -private: - /// The instructions contained in this basic block - std::vector Instructions; - - /// List of successors - links_container Successors; - - /// List of predecessors - /// - /// \note This field is initialized only after ABIFunction::finalize is called - links_container Predecessors; - - /// Reference to the corresponding basic block - llvm::BasicBlock *BB; - - /// Flag to identify return basic blocks - bool IsReturn; - -public: - ABIIRBasicBlock(llvm::BasicBlock *BB) : BB(BB), IsReturn(false) {} - -public: - /// \brief Purge basic block content - void clear() { - revng_assert(Predecessors.empty()); - Instructions.clear(); - Successors.clear(); - IsReturn = false; - } - - bool isPartOfFinalResults() const { return IsReturn; } - void setReturn() { IsReturn = true; } - - void append(ABIIRInstruction I) { Instructions.push_back(std::move(I)); } - - void addSuccessor(ABIIRBasicBlock *Successor) { - Successors.push_back(Successor); - } - - size_t successor_size() const { return Successors.size(); } - links_const_iterator successor_begin() const { return Successors.begin(); } - links_const_iterator successor_end() const { return Successors.end(); } - links_const_range successors() const { - return llvm::make_range(Successors.begin(), Successors.end()); - } - links_iterator successor_begin() { return Successors.begin(); } - links_iterator successor_end() { return Successors.end(); } - links_range successors() { - return llvm::make_range(Successors.begin(), Successors.end()); - } - - size_t predecessor_size() const { return Predecessors.size(); } - links_const_iterator predecessor_begin() const { - return Predecessors.begin(); - } - links_const_iterator predecessor_end() const { return Predecessors.end(); } - links_const_range predecessors() const { - return llvm::make_range(Predecessors.begin(), Predecessors.end()); - } - links_iterator predecessor_begin() { return Predecessors.begin(); } - links_iterator predecessor_end() { return Predecessors.end(); } - links_range predecessors() { - return llvm::make_range(Predecessors.begin(), Predecessors.end()); - } - - template - size_t next_size() const { - return Forward ? successor_size() : predecessor_size(); - } - template - links_const_range next() const { - return Forward ? successors() : predecessors(); - } - - size_t size() const { return Instructions.size(); } - - iterator begin() { return Instructions.begin(); } - iterator end() { return Instructions.end(); } - const_iterator begin() const { return Instructions.begin(); } - const_iterator end() const { return Instructions.end(); } - - reverse_iterator rbegin() { return Instructions.rbegin(); } - reverse_iterator rend() { return Instructions.rend(); } - const_reverse_iterator rbegin() const { return Instructions.rbegin(); } - const_reverse_iterator rend() const { return Instructions.rend(); } - - llvm::BasicBlock *basicBlock() const { return BB; } - - void - dump(const llvm::Module *M, const char *Prefix = "") const debug_function { - dump(dbg, M, Prefix); - } - - template - void dump(T &Output, const llvm::Module *M, const char *Prefix = "") const { - - Output << Prefix << "From basic block " << ::getName(BB); - if (IsReturn) - Output << " [IsReturn]"; - Output << "\n"; - - if (not Predecessors.empty()) { - Output << Prefix << "Predecessors:\n"; - for (const ABIIRBasicBlock *Predecessor : Predecessors) { - Output << Prefix << " " << ::getName(Predecessor->basicBlock()) - << "\n"; - } - Output << Prefix << "\n"; - } - - Output << Prefix << "Instructions:\n"; - for (const ABIIRInstruction &I : Instructions) { - Output << Prefix << " "; - I.dump(Output, M); - Output << "\n"; - } - Output << "\n"; - - if (not Successors.empty()) { - Output << Prefix << "Successors:\n"; - for (const ABIIRBasicBlock *Successor : Successors) - Output << Prefix << " " << ::getName(Successor->basicBlock()) << "\n"; - Output << Prefix << "\n"; - } - } -}; - -/// \brief The ABI IR, a container of ABIIRBasicBlocks -class ABIFunction { -public: - template - using VectorOfPairs = std::vector>; - using calls_container = VectorOfPairs; - - using calls_iterator = calls_container::iterator; - using calls_range = llvm::iterator_range; - - using calls_const_iterator = calls_container::const_iterator; - using calls_const_range = llvm::iterator_range; - - using returns_container = std::vector; - using returns_iterator = returns_container::iterator; - using returns_range = llvm::iterator_range; - - using returns_const_iterator = returns_container::const_iterator; - using returns_const_range = llvm::iterator_range; - -private: - /// Storage for ABI IR basic blocks, associated to their original counterpart - /// - /// \note Don't move after Entry - std::map BBMap; - - /// Pointer to the entry basic block of this function - llvm::BasicBlock *Entry; - ABIIRBasicBlock *IREntry; - - /// Vector of all the function calls in this function - calls_container Calls; - - /// Vector of all the return basic blocks - returns_container FinalBBs; - -public: - ABIFunction(llvm::BasicBlock *Entry) : - Entry(Entry), - IREntry(&BBMap.emplace(Entry, ABIIRBasicBlock(Entry)).first->second) {} - - ABIIRBasicBlock *entry() const { return IREntry; } - - size_t size() const { return BBMap.size(); } - - /// \brief Purge all the data in this IR - void reset() { - BBMap.clear(); - Calls.clear(); - FinalBBs.clear(); - IREntry = &BBMap.emplace(Entry, ABIIRBasicBlock(Entry)).first->second; - } - - /// \brief Finalize the IR after initially populating it - /// - /// This method basically populates the backward links of the CFG, identifies - /// all the function calls and ensures the entry basic block has no inbound - /// edges. - void finalize(); - - /// \brief Identify calls leading to contradition - std::set incoherentCalls(); - - std::set writtenRegisters() const; - - ABIIRBasicBlock &get(llvm::BasicBlock *BB) { - auto It = BBMap.find(BB); - if (It != BBMap.end()) - return It->second; - - return BBMap.emplace(BB, ABIIRBasicBlock(BB)).first->second; - } - - const ABIIRBasicBlock &get(llvm::BasicBlock *BB) const { - auto It = BBMap.find(BB); - revng_assert(It != BBMap.end()); - return It->second; - } - - calls_const_range calls() const { - return llvm::make_range(Calls.begin(), Calls.end()); - } - - size_t calls_size() const { return Calls.size(); } - - returns_const_range finals() const { - return llvm::make_range(FinalBBs.begin(), FinalBBs.end()); - } - - size_t finals_size() const { return FinalBBs.size(); } - - bool verify() const debug_function; - - /// \brief Dump a GraphViz file on stdout representing this function - void dumpDot() const debug_function; - - void dump(const llvm::Module *M) const debug_function { dump(dbg, M); } - - template - void dump(T &Output, const llvm::Module *M) const { - std::set Visited; - std::set Entries; - - for (auto &P : BBMap) - if (P.second.predecessor_size() == 0) - Entries.insert(&P.second); - - for (const ABIIRBasicBlock *BB : Entries) { - if (Visited.count(BB) != 0) - continue; - - std::stack WorkList; - WorkList.push(BB); - while (!WorkList.empty()) { - const ABIIRBasicBlock *Current = WorkList.top(); - WorkList.pop(); - - Visited.insert(Current); - - Current->dump(Output, M, " "); - - for (const ABIIRBasicBlock *Successor : Current->successors()) - if (Visited.count(Successor) == 0) - WorkList.push(Successor); - } - } - } -}; - -/// \brief Identify calls leading to contradition -/// -/// \note This is implemented in incoherentcallsanalysis.cpp -std::set -computeIncoherentCalls(ABIIRBasicBlock *Entry, - std::vector &Extremals); - -template -inline T instructionRange(ABIIRBasicBlock *BB); - -template<> -inline ABIIRBasicBlock::range -instructionRange(ABIIRBasicBlock *BB) { - ABIIRBasicBlock::iterator InstructionIt = BB->begin(); - return llvm::make_range(InstructionIt, BB->end()); -} - -template<> -inline ABIIRBasicBlock::reverse_range -instructionRange(ABIIRBasicBlock *BB) { - ABIIRBasicBlock::reverse_iterator InstructionIt = BB->rbegin(); - return llvm::make_range(InstructionIt, BB->rend()); -} - -} // namespace StackAnalysis - -// Provide graph traits for usage with, e.g., llvm::ReversePostOrderTraversal -namespace llvm { - -template<> -struct GraphTraits { - using NodeRef = StackAnalysis::ABIIRBasicBlock *; - using ChildIteratorType = StackAnalysis::ABIIRBasicBlock::links_iterator; - - static NodeRef getEntryNode(StackAnalysis::ABIIRBasicBlock *BB) { return BB; } - - static inline ChildIteratorType - child_begin(StackAnalysis::ABIIRBasicBlock *N) { - return N->successors().begin(); - } - - static inline ChildIteratorType child_end(StackAnalysis::ABIIRBasicBlock *N) { - return N->successors().end(); - } -}; - -template<> -struct GraphTraits> { - using NodeRef = StackAnalysis::ABIIRBasicBlock *; - using ChildIteratorType = StackAnalysis::ABIIRBasicBlock::links_iterator; - - static NodeRef getEntryNode(StackAnalysis::ABIIRBasicBlock *BB) { return BB; } - - static inline ChildIteratorType - child_begin(StackAnalysis::ABIIRBasicBlock *N) { - return N->predecessor_begin(); - } - - static inline ChildIteratorType child_end(StackAnalysis::ABIIRBasicBlock *N) { - return N->predecessor_end(); - } -}; - -} // namespace llvm diff --git a/lib/StackAnalysis/ASSlot.h b/lib/StackAnalysis/ASSlot.h deleted file mode 100644 index e860d5b39..000000000 --- a/lib/StackAnalysis/ASSlot.h +++ /dev/null @@ -1,270 +0,0 @@ -#pragma once - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include - -#include "llvm/IR/GlobalVariable.h" -#include "llvm/IR/Module.h" - -#include "revng/Support/Debug.h" -#include "revng/Support/IRHelpers.h" - -extern Logger<> SaDiffLog; - -// #define EXPENSIVE_ASSERTIONS - -namespace StackAnalysis { - -template -struct debug_cmp { - /// \brief Perform a comparison between \p This and \p Other printing out all - /// the differences - /// - /// We need this so that types not fully under our control (e.g., - /// UnionMonotoneSet) can implement this method too. This has to be part - /// of a struct so that we can perform partial template specialization. - static unsigned cmp(const T &This, const T &Other, const llvm::Module *M) { - return This.template cmp(Other, M); - } -}; - -/// \brief Assert LHS.lowerThanOrEqual(RHS), and, if not, print the differences -template -inline void -assertLowerThanOrEqual(const T &LHS, const T &RHS, const llvm::Module *M) { -#if defined(NDEBUG) && defined(EXPENSIVE_ASSERTIONS) - bool Result = LHS.lowerThanOrEqual(RHS); - if (!Result) { - SaDiffLog.enable(); - debug_cmp::cmp(LHS, RHS, M); - revng_abort(); - } -#endif -} - -// Note: the following classes are not part of the Intraprocedural namespace. - -/// \brief Identifier of an address space -class ASID { -private: - uint32_t ID; - -private: - enum { - /// The address space representing the CPU state, registers in particular - CPUAddressSpaceID, - /// The address space containing literal addresses (and numbers) - GlobalID, - /// The stack frame we're tracking (SP0) - LastStackID, - InvalidID, - LastID - }; - -public: - explicit ASID(uint32_t ID) : ID(ID) { revng_assert(ID < LastID); } - - // Factory methods - static ASID invalidID() { return ASID(InvalidID); } - static ASID cpuID() { return ASID(CPUAddressSpaceID); } - static ASID stackID() { return ASID(LastStackID); } - static ASID globalID() { return ASID(GlobalID); } - -public: - uint32_t id() const { return ID; } - - bool operator<(const ASID &Other) const { return ID < Other.ID; } - bool operator==(const ASID &Other) const { return ID == Other.ID; } - bool operator!=(const ASID &Other) const { return not(*this == Other); } - - size_t hash() const; - - /// \brief Perform a comparison according to the analysis' lattice - /// - /// \note Address spaces identifiers are not comparable, they are just unique - /// identifiers. The CPU address space is not "more informative" or - /// "less conservative" than the GLB address space. Therefore we just - /// perform an equality comparison here. - bool lowerThanOrEqual(const ASID &Other) const { return ID == Other.ID; } - - void dump() const debug_function { dump(dbg); } - - template - void dump(T &Output) const { - switch (ID) { - case CPUAddressSpaceID: - Output << "CPU"; - break; - case LastStackID: - Output << "SP0"; - break; - case GlobalID: - Output << "GLB"; - break; - case InvalidID: - Output << "INV"; - break; - } - } - - bool isStack() const { return ID == LastStackID; } - bool isValid() const { return ID != InvalidID; } -}; - -/// \brief Class representing the address of an address space slot -class ASSlot { -private: - ASID AS; - int32_t Offset; - -private: - ASSlot(ASID ID, int32_t Offset) : AS(ID), Offset(Offset) {} - -public: - static ASSlot invalid() { return ASSlot(ASID::invalidID(), 0); } - static ASSlot create(ASID ID, int32_t Offset) { - revng_assert(ID.isValid()); - return ASSlot(ID, Offset); - } - -public: - /// \brief Perform a comparison according to the analysis' lattice - bool lowerThanOrEqual(const ASSlot &Other) const; - - template - unsigned cmp(const ASSlot &Other, const llvm::Module *M) const; - - size_t hash() const; - - bool operator==(const ASSlot &Other) const { - return std::tie(AS, Offset) == std::tie(Other.AS, Other.Offset); - } - - bool operator!=(const ASSlot &Other) const { return not(*this == Other); } - - bool operator<(const ASSlot &Other) const { - auto ThisTuple = std::make_pair(AS.id(), Offset); - auto OtherTuple = std::make_pair(Other.AS.id(), Other.Offset); - return ThisTuple < OtherTuple; - } - - int32_t offset() const { return Offset; } - ASID addressSpace() const { return AS; } - bool isInvalid() const { return AS == ASID::invalidID(); } - - /// \brief Add a constant to the offset associated with this slot - void add(int32_t Addend) { Offset += Addend; } - - /// \brief Mask the offset associated to this slot with a value - void mask(uint64_t Operand) { Offset = Offset & Operand; } - - void dump(const llvm::Module *M) const debug_function { dump(M, dbg); } - - template - void dump(const llvm::Module *M, T &Output) const { - AS.dump(Output); - if (Offset >= 0) - Output << "+"; - dumpOffset(M, AS, Offset, Output); - } - -public: - static void dumpOffset(const llvm::Module *M, ASID AS, int32_t Offset) { - dumpOffset(M, AS, Offset, dbg); - } - - template - static void - dumpOffset(const llvm::Module *M, ASID AS, int32_t Offset, T &Output) { - if (M != nullptr && AS == ASID::cpuID()) { - auto Name = csvNameByOffset(Offset, M); - if (Name) { - Output << *Name; - return; - } - - Output << "alloca_"; - } - - if (Offset < 0) { - Offset = -Offset; - Output << "-"; - } - Output << "0x" << std::hex << Offset << std::dec; - } - -private: - static llvm::Optional - csvNameByOffset(int32_t Offset, const llvm::Module *M) { - using namespace llvm; - - revng_assert(Offset != 0); - - if (Offset == 1) - return { "pc" }; - - const char *MDName = "revng.input.architecture"; - NamedMDNode *InputArchMD = M->getNamedMetadata(MDName); - auto *Tuple = dyn_cast(InputArchMD->getOperand(0)); - - QuickMetadata QMD(M->getContext()); - Offset = Offset - 2; - const auto *ABIRegisters = QMD.extract(Tuple, 5); - if (Offset >= static_cast(ABIRegisters->getNumOperands())) - return llvm::Optional(); - - const auto &Operand = ABIRegisters->getOperand(Offset); - return QMD.extract(Operand.get()).str(); - } -}; - -} // namespace StackAnalysis - -namespace std { - -template<> -struct hash { - size_t operator()(const StackAnalysis::ASSlot &K) const { return K.hash(); } -}; - -template<> -struct hash { - size_t operator()(const StackAnalysis::ASID &K) const { return K.hash(); } -}; - -} // namespace std - -// All of these could probably be reimplemented using lambdas, however I haven't -// assessed the performance of lambdas, and this is quite performance critical, -// therefore I don't want to risk for now. Moreover, lambdas are not super -// elegant either. Coroutines would be the best here. - -/// Compute \p Expression, if non-zero: -/// -/// * check if Diff == true, if so run \p OnDiff -/// * check if EarlyExit == true, if so return 1, otherwise increment of \p -/// Expression the Result variable -/// -/// This is supposed to be used for performing a comparison between objects, -/// possibly printing a diagnostics on why they are different, and allowing the -/// user to choose whether to return on the first call to ROA that evaluates to -/// non-zero or proceed and accumulate the number of differences in the Result -/// variable. -#define ROA(Expression, OnDiff) \ - do { \ - if (unsigned C = (Expression)) { \ - \ - if (SaDiffLog.isEnabled() && Diff) { \ - OnDiff \ - } \ - \ - if (EarlyExit) { \ - return 1; \ - } else { \ - Result += C; \ - } \ - } \ - } while (false) diff --git a/lib/StackAnalysis/BasicBlockInstructionPair.h b/lib/StackAnalysis/BasicBlockInstructionPair.h deleted file mode 100644 index 1022f5d9f..000000000 --- a/lib/StackAnalysis/BasicBlockInstructionPair.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include "revng/Support/Debug.h" -#include "revng/Support/IRHelpers.h" - -namespace llvm { -class BasicBlock; -class Instruction; -} // namespace llvm - -namespace StackAnalysis { - -/// \brief std::pair on steroids -class BasicBlockInstructionPair { -public: - BasicBlockInstructionPair() : BB(nullptr), I(nullptr) {} - BasicBlockInstructionPair(llvm::BasicBlock *BB, llvm::Instruction *I) : - BB(BB), I(I) {} - - bool isNull() const { return I == nullptr || BB == nullptr; } - - bool operator<(const BasicBlockInstructionPair &Other) const { - return std::tie(BB, I) < std::tie(Other.BB, Other.I); - } - - bool operator==(const BasicBlockInstructionPair &Other) const { - return std::tie(BB, I) == std::tie(Other.BB, Other.I); - } - - bool operator!=(const BasicBlockInstructionPair &Other) const { - return !(*this == Other); - } - - void dump() const debug_function { dump(dbg); } - - template - void dump(T &Output) const { - Output << getName(BB) << ":" << getName(I); - } - -protected: - llvm::BasicBlock *BB; - llvm::Instruction *I; -}; - -/// \brief Represent a call site within a function -/// -/// \note caller() doesn't return callInstruction()->getParent(), but entry -/// basic block of the original function containing this call site. -class CallSite : public BasicBlockInstructionPair { -public: - CallSite() : BasicBlockInstructionPair() {} - CallSite(llvm::BasicBlock *BB, llvm::Instruction *I) : - BasicBlockInstructionPair(BB, I) {} - - bool belongsTo(llvm::BasicBlock *OtherBB) const { return OtherBB == BB; } - llvm::BasicBlock *caller() const { return BB; } - llvm::Instruction *callInstruction() const { return I; } -}; - -class FunctionCall : public BasicBlockInstructionPair { -public: - FunctionCall() : BasicBlockInstructionPair() {} - FunctionCall(llvm::BasicBlock *BB, llvm::Instruction *I) : - BasicBlockInstructionPair(BB, I) {} - - llvm::BasicBlock *callee() const { return BB; } - llvm::Instruction *callInstruction() const { return I; } -}; - -/// \brief Represent a branch instruction within a function -/// -/// \note caller() doesn't return callInstruction()->getParent(), but entry -/// basic block of the original function containing this branch. -class Branch : public BasicBlockInstructionPair { -public: - Branch() : BasicBlockInstructionPair() {} - Branch(llvm::BasicBlock *BB, llvm::Instruction *I) : - BasicBlockInstructionPair(BB, I) {} - - bool belongsTo(llvm::BasicBlock *OtherBB) const { return OtherBB == BB; } - llvm::Instruction *branch() const { return I; } - llvm::BasicBlock *entry() const { return BB; } -}; - -inline void writeToLog(Logger &This, const CallSite &Other, int) { - Other.dump(This); -} - -} // namespace StackAnalysis diff --git a/lib/StackAnalysis/CMakeLists.txt b/lib/StackAnalysis/CMakeLists.txt index 6053eff7b..46ce882fc 100644 --- a/lib/StackAnalysis/CMakeLists.txt +++ b/lib/StackAnalysis/CMakeLists.txt @@ -2,38 +2,13 @@ # This file is distributed under the MIT License. See LICENSE.md for details. # -# Generate classes for the ABI data flow analyses -set(ABIDATAFLOWS_SOURCES - "${CMAKE_CURRENT_SOURCE_DIR}/ABIDataFlows-header.inc" - "${CMAKE_CURRENT_SOURCE_DIR}/DeadRegisterArgumentsOfFunction.dot" - "${CMAKE_CURRENT_SOURCE_DIR}/DeadReturnValuesOfFunctionCall.dot" - "${CMAKE_CURRENT_SOURCE_DIR}/RegisterArgumentsOfFunctionCall.dot" - "${CMAKE_CURRENT_SOURCE_DIR}/UsedArgumentsOfFunction.dot" - "${CMAKE_CURRENT_SOURCE_DIR}/UsedReturnValuesOfFunctionCall.dot" - "${CMAKE_CURRENT_SOURCE_DIR}/UsedReturnValuesOfFunction.dot") -add_custom_command(OUTPUT ABIDataFlows.h - COMMAND "${CMAKE_SOURCE_DIR}/scripts/monotone_framework.py" - --call-arcs ${ABIDATAFLOWS_SOURCES} > ABIDataFlows.h - DEPENDS "${CMAKE_SOURCE_DIR}/scripts/monotone_framework.py" - ${ABIDATAFLOWS_SOURCES} - VERBATIM) -add_custom_target(abidataflows DEPENDS ABIDataFlows.h) - add_subdirectory(ABIAnalyses) revng_add_analyses_library_internal(revngStackAnalysis AAWriterPass.cpp ABI.cpp ABIDetectionPass.cpp - ABIIR.cpp - Cache.cpp - Element.cpp - FunctionABI.cpp - FunctionsSummary.cpp - IncoherentCallsAnalysis.cpp IndirectBranchInfoPrinterPass.cpp - InterproceduralAnalysis.cpp - Intraprocedural.cpp PromoteGlobalToLocalVars.cpp SegregateDirectStackAccesses.cpp StackAnalysis.cpp) @@ -46,9 +21,3 @@ target_link_libraries(revngStackAnalysis revngSupport revngModel ${LLVM_LIBRARIES}) - -target_include_directories(revngStackAnalysis - PRIVATE - "${CMAKE_CURRENT_BINARY_DIR}") - -add_dependencies(revngStackAnalysis abidataflows) diff --git a/lib/StackAnalysis/Cache.cpp b/lib/StackAnalysis/Cache.cpp deleted file mode 100644 index 0ff9f3aff..000000000 --- a/lib/StackAnalysis/Cache.cpp +++ /dev/null @@ -1,398 +0,0 @@ -/// \file Cache.cpp -/// \brief - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h" - -#include "Cache.h" - -using llvm::AllocaInst; -using llvm::BasicBlock; -using llvm::BinaryOperator; -using llvm::BlockAddress; -using llvm::CallInst; -using llvm::Constant; -using llvm::dyn_cast; -using llvm::Function; -using llvm::GlobalVariable; -using llvm::Instruction; -using llvm::isa; -using llvm::LoadInst; -using llvm::Module; -using llvm::Optional; -using llvm::SelectInst; -using llvm::StoreInst; -using llvm::Use; -using llvm::User; - -static Logger<> SaPreprocess("sa-preprocess"); -Logger<> SaLog("sa"); - -namespace StackAnalysis { - -/// \brief Check it two loads are equivalent (load from same CSV, no stores in -/// between) -static bool areEquivalent(const LoadInst *A, const LoadInst *B) { - if (A == B) - return true; - - const llvm::Value *Address = A->getPointerOperand(); - if (B->getPointerOperand() != Address) - return false; - - if (not isa(Address)) - return false; - - const BasicBlock *BB = A->getParent(); - if (B->getParent() != BB) - return false; - - for (const Instruction &I : *BB) { - if (&I == A) { - break; - } else if (&I == B) { - std::swap(A, B); - break; - } - } - - auto EndIt = B->getIterator(); - for (auto It = A->getIterator(); It != EndIt; It++) - if (auto *Store = dyn_cast(&*It)) - if (Store->getPointerOperand() == Address) - return false; - - return true; -} - -static bool mayAlias(const llvm::Value *A, const llvm::Value *B) { - return not((isa(A) or isa(B)) and A != B); -} - -static bool noWritesTo(const Instruction *Start, - const Instruction *End, - const llvm::Value *Address) { - if (Start->getParent() != End->getParent()) - return false; - - for (const Instruction &I : - llvm::make_range(Start->getIterator(), End->getIterator())) - if (auto *Store = dyn_cast(&I)) - if (mayAlias(Store->getPointerOperand(), Address)) - return false; - return true; -} - -void Cache::identifyPartialStores(const Function *F) { - // - // Partial store - // - // a = rax & 0xffff0000 - // b = 0xaaaa - // rax = a | b - - // Look for partial stores in registers - for (const GlobalVariable &CSV : F->getParent()->globals()) { - for (const Use &U : CSV.uses()) { - - const auto *UserI = dyn_cast(U.getUser()); - if (UserI == nullptr || UserI->getParent()->getParent() != F) - continue; - - if (const auto *Store = dyn_cast(U.getUser())) { - if (U.getOperandNo() != StoreInst::getPointerOperandIndex()) - continue; - - // We have a store - const llvm::Value *ToStoreValue = Store->getValueOperand(); - const auto *ToStore = dyn_cast(ToStoreValue); - if (ToStore == nullptr || ToStore->getOpcode() != Instruction::Or) - continue; - - // We're storing the "or" of two values, one of the two has to be the - // same as the destination register, with optional partial suppression - const LoadInst *LoadFromSame = nullptr; - for (unsigned OperandIndex = 0; - OperandIndex < ToStore->getNumOperands(); - OperandIndex++) { - std::set Visited; - bool PartialClobber = false; - const llvm::Value *Operand = ToStore->getOperand(OperandIndex); - while (true) { - if (Visited.count(Operand) != 0) - break; - Visited.insert(Operand); - - if (const auto *TheLoad = dyn_cast(Operand)) { - - // We reached a load, is it from the same CSV where we were - // storing? Also ensure no one wrote to that CSV when we rewrite - // the (partially clobbered) old value. - if (TheLoad->getPointerOperand() == &CSV and PartialClobber - and noWritesTo(TheLoad, Store, &CSV)) { - revng_assert(LoadFromSame == nullptr - or areEquivalent(LoadFromSame, TheLoad)); - LoadFromSame = TheLoad; - } - - // In any case stop - break; - } else if (const auto *BinOp = dyn_cast(Operand)) { - - // We only allow Ands with constants - // TODO: allow shifts? - if (BinOp->getOpcode() != Instruction::And) - break; - - const llvm::Value *FreeOp = BinOp->getOperand(0); - const llvm::Value *OtherOp = BinOp->getOperand(1); - if (BinOp->isCommutative() && isa(FreeOp)) - std::swap(FreeOp, OtherOp); - - // We have an And with a Constant, let's proceed towards the free - // operand, in all other cases skip - if (isa(OtherOp)) { - Operand = FreeOp; - PartialClobber = true; - } else { - break; - } - - } else { - break; - } - } - } - - if (LoadFromSame != nullptr) - IdentityLoads.insert(LoadFromSame); - } - } - } -} - -void Cache::identifyIdentityLoads(const Function *F) { - // - // Identity load - // - // a = rax - // rax = a - // rax = a - - // Look for identity loads - for (const BasicBlock &BB : *F) { - for (const Instruction &I : BB) { - if (const auto *Store = dyn_cast(&I)) { - - const llvm::Value *StoredValue = Store->getValueOperand(); - unsigned StoreSize = StoredValue->getType()->getIntegerBitWidth(); - const llvm::Value *Address = Store->getPointerOperand(); - const llvm::Value *NextOperand = StoredValue; - - while (NextOperand != nullptr) { - const llvm::Value *Operand = NextOperand; - NextOperand = nullptr; - - if (auto *Load = dyn_cast(Operand)) { - if (Load->getPointerOperand() == Address - and noWritesTo(Load, Store, Address)) - IdentityStores.insert(Store); - } else if (auto *ZExt = dyn_cast(Operand)) { - NextOperand = ZExt->getOperand(0); - } else if (auto *Trunc = dyn_cast(Operand)) { - if (Trunc->getType()->getIntegerBitWidth() >= StoreSize) - NextOperand = Trunc->getOperand(0); - } - } - - } else if (const auto *Load = dyn_cast(&I)) { - bool IsIdentityStore = true; - bool AtLeastOneStore = false; - std::set Visited; - std::queue WorkList; - - const llvm::Value *Address = Load->getPointerOperand(); - - for (const Use &TheUse : Load->uses()) - WorkList.push(&TheUse); - - while (not WorkList.empty()) { - const Use *I = WorkList.back(); - const User *TheUser = I->getUser(); - WorkList.pop(); - - // Don't visit twice the same instruction - if (Visited.count(I) != 0) { - IsIdentityStore = false; - break; - } - Visited.insert(I); - - // We whitelist only stores to the original value and select - // instructions - bool Proceed = false; - if (const auto *TheStore = dyn_cast(TheUser)) { - Proceed = (I->getOperandNo() == 0 - and TheStore->getPointerOperand() == Address - and noWritesTo(Load, TheStore, Address)); - AtLeastOneStore = true; - } else if (isa(TheUser)) { - Proceed = I->getOperandNo() != 0; - } - - if (not Proceed) { - IsIdentityStore = false; - break; - } - - for (const Use &TheUse : TheUser->uses()) - WorkList.push(&TheUse); - } - - if (IsIdentityStore && AtLeastOneStore) - IdentityLoads.insert(Load); - } - } - } -} - -// TODO: we might want to record link register as slots and call them -// ReturnAddressSlot -void Cache::identifyLinkRegisters(const Module *M) { - // - // For each function call identify where the return address is being stored - // - Function *FunctionCallFunction = M->getFunction("function_call"); - - if (not FunctionCallFunction->user_empty()) { - std::map LinkRegisterStats; - std::map LinkRegistersMap; - for (User *U : FunctionCallFunction->users()) { - if (auto *Call = dyn_cast(U)) { - revng_assert(isCallTo(Call, "function_call")); - auto *LinkRegister = dyn_cast(Call->getArgOperand(3)); - LinkRegisterStats[LinkRegister]++; - - // The callee might be unknown - if (auto *BBA = dyn_cast(Call->getArgOperand(0))) { - BasicBlock *Callee = BBA->getBasicBlock(); - revng_assert(LinkRegistersMap.count(Callee) == 0 - || LinkRegistersMap.at(Callee) == LinkRegister); - LinkRegistersMap[Callee] = LinkRegister; - } - } - } - - // Identify a default storage for the return address (the most common one) - if (LinkRegisterStats.size() == 1) { - DefaultLinkRegister = LinkRegisterStats.begin()->first; - } else { - std::pair Max = { nullptr, 0 }; - for (auto &P : LinkRegisterStats) { - if (P.second > Max.second) - Max = P; - } - revng_assert(Max.first != nullptr && Max.second != 0); - DefaultLinkRegister = Max.first; - } - } -} - -void Cache::assignCPUIndices(Function *F, GeneratedCodeBasicInfo *GCBI) { - // Enumerate CPU state and allocas - CSVToIndexMap.clear(); - IndexToCSVMap.clear(); - - // Skip 0, keep it as "invalid value" - int32_t I = 1; - - IndexToCSVMap[I++] = GCBI->pcReg(); - - // Go through global variables first - for (llvm::GlobalVariable *GV : GCBI->abiRegisters()) - IndexToCSVMap[I++] = GV; - - CSVCount = I; - - // Look for AllocaInst at the beginning of the root function - llvm::BasicBlock *Entry = &*F->begin(); - auto It = Entry->begin(); - while (It != Entry->end() and isa(&*It)) { - IndexToCSVMap[I] = &*It; - - I++; - It++; - } - - for (auto &P : IndexToCSVMap) - CSVToIndexMap[P.second] = P.first; -} - -Cache::Cache(Function *F, GeneratedCodeBasicInfo *GCBI) : - DefaultLinkRegister(nullptr) { - assignCPUIndices(F, GCBI); - identifyPartialStores(F); - identifyIdentityLoads(F); - identifyLinkRegisters(F->getParent()); - - // Dump the results - if (SaPreprocess.isEnabled()) { - SaPreprocess << "IdentityStores:\n"; - for (const StoreInst *I : IdentityStores) - SaPreprocess << I << "\n"; - SaPreprocess << DoLog; - - SaPreprocess << "IdentityLoads:\n"; - for (const LoadInst *I : IdentityLoads) - SaPreprocess << I << "\n"; - SaPreprocess << DoLog; - - SaPreprocess << "DefaultLinkRegister: " << DefaultLinkRegister << DoLog; - } -} - -Optional -Cache::get(BasicBlock *Function) const { - auto It = Results.find(Function); - if (It != Results.end()) - return { &It->second }; - - return Optional(); -} - -bool Cache::update(BasicBlock *Function, - const IntraproceduralFunctionSummary &Result) { - - if (SaLog.isEnabled()) { - SaLog << "Cache.update(" << getName(Function) << ") with value\n"; - Result.dump(getModule(Function), SaLog); - SaLog << DoLog; - } - - auto It = Results.find(Function); - if (It == Results.end()) { - Results.emplace(std::make_pair(Function, Result.copy())); - return false; - } else { - auto &Summary = It->second; - - Intraprocedural::Element &Old = Summary.FinalState; - const Intraprocedural::Element &New = Result.FinalState; - - // We should never put in the cache something more precise than what we had - // before or the analysis might not terminate. In any case, we will perform - // the analysis only once most of the times. The main exception are - // recursive function calls which will temporarily inject in the cache a - // temporary top entry, which will be overwritten later on. - revng_assert(New.lowerThanOrEqual(Old)); - - It->second = Result.copy(); - - return not Old.lowerThanOrEqual(New); - } -} - -} // namespace StackAnalysis diff --git a/lib/StackAnalysis/Cache.h b/lib/StackAnalysis/Cache.h deleted file mode 100644 index f0d286cfd..000000000 --- a/lib/StackAnalysis/Cache.h +++ /dev/null @@ -1,128 +0,0 @@ -#pragma once - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include "Element.h" -#include "IntraproceduralFunctionSummary.h" - -class GeneratedCodeBasicInfo; - -namespace StackAnalysis { - -/// \brief Cache for the result of the analysis of a function -/// -/// This cache keeps track of three pieces of information: -/// -/// * the result of the analysis of a function. -/// * the set of "fake", "noreturn" and "indirect tail call" functions. -/// * the association between each function and its return register. -class Cache { -private: - /// \brief For each function, the result of the intraprocedural analysis - std::map Results; - - /// \brief For each function, its link register (or nullptr for top of the - /// stack) - std::map LinkRegisters; - - /// \brief The elected default link register (i.e., the most common) - llvm::GlobalVariable *DefaultLinkRegister; - - std::set FakeFunctions; - std::set NoReturnFunctions; - std::set IndirectTailCallFunctions; - - std::set IdentityLoads; - std::set IdentityStores; - - std::map CSVToIndexMap; - std::map IndexToCSVMap; - int32_t CSVCount; - -public: - /// \brief Identify default storage for link register, identity loads - Cache(llvm::Function *F, GeneratedCodeBasicInfo *GCBI); - - int32_t getCPUIndex(const llvm::User *U) const { return CSVToIndexMap.at(U); } - bool isCPU(const llvm::User *U) const { return CSVToIndexMap.count(U) != 0; } - bool isCSV(const llvm::User *U) const { - return CSVToIndexMap.count(U) != 0 and CSVToIndexMap.at(U) < CSVCount; - } - llvm::GlobalVariable *getCSVByIndex(int32_t I) const { - return llvm::cast(IndexToCSVMap.at(I)); - } - bool isCSVIndex(int32_t I) const { - return IndexToCSVMap.count(I) != 0 and I < CSVCount; - } - - bool isFakeFunction(llvm::BasicBlock *Function) const { - return FakeFunctions.count(Function) != 0; - } - - void markAsFake(llvm::BasicBlock *Function) { - FakeFunctions.insert(Function); - } - - bool isNoReturnFunction(llvm::BasicBlock *Function) const { - return NoReturnFunctions.count(Function) != 0; - } - - void markAsNoReturn(llvm::BasicBlock *Function) { - NoReturnFunctions.insert(Function); - } - - /// \brief Query the cache for the result of the analysis for a specific - /// function - /// - /// \return the matching result, if available. - llvm::Optional - get(llvm::BasicBlock *Function) const; - - /// \brief Insert (or update) in the cache an entry for the function - /// \p Function - bool update(llvm::BasicBlock *Function, - const IntraproceduralFunctionSummary &Result); - - /// \brief Get the link register for the function identified by \p Function - /// - /// \return a pointer to the CSV representing the link register for - /// \p Function or nullptr, in case there's no link register (i.e., - /// return addresses are stored on the stack). - llvm::GlobalVariable *getLinkRegister(llvm::BasicBlock *Function) const { - if (DefaultLinkRegister == nullptr) - return nullptr; - - if (LinkRegisters.size() == 0) { - return DefaultLinkRegister; - } else { - auto It = LinkRegisters.find(Function); - if (It == LinkRegisters.end()) - return DefaultLinkRegister; - else - return It->second; - } - } - - /// An identity load is a load from a CSV whose value ends up (potentially - /// truncated and/or in OR with another value) exclusively in the same CSV. - /// - /// Such loads should not be considered as actually "reading" a register. - bool isIdentityLoad(const llvm::LoadInst *L) const { - return IdentityLoads.count(L) != 0; - } - - /// An identity store is a store associated to an identity load. - bool isIdentityStore(const llvm::StoreInst *S) const { - return IdentityStores.count(S) != 0; - } - -private: - void assignCPUIndices(llvm::Function *F, GeneratedCodeBasicInfo *GCBI); - void identifyPartialStores(const llvm::Function *F); - void identifyIdentityLoads(const llvm::Function *F); - void identifyLinkRegisters(const llvm::Module *M); -}; - -} // namespace StackAnalysis diff --git a/lib/StackAnalysis/DeadRegisterArgumentsOfFunction.dot b/lib/StackAnalysis/DeadRegisterArgumentsOfFunction.dot deleted file mode 100644 index 7359f7961..000000000 --- a/lib/StackAnalysis/DeadRegisterArgumentsOfFunction.dot +++ /dev/null @@ -1,18 +0,0 @@ -# -# This file is distributed under the MIT License. See LICENSE.md for details. -# - -digraph DeadRegisterArgumentsOfFunction { - NoOrDead; - Maybe [peripheries=2]; - Unknown; - - # Lattice - NoOrDead->Maybe; - Maybe->Unknown; - - # Transfer functions - Maybe->NoOrDead [label="Write"]; - Maybe->Unknown [label="Read"]; - Maybe->Unknown [label="UnknownFunctionCall"]; -} diff --git a/lib/StackAnalysis/DeadReturnValuesOfFunctionCall.dot b/lib/StackAnalysis/DeadReturnValuesOfFunctionCall.dot deleted file mode 100644 index 8c6e225b6..000000000 --- a/lib/StackAnalysis/DeadReturnValuesOfFunctionCall.dot +++ /dev/null @@ -1,19 +0,0 @@ -# -# This file is distributed under the MIT License. See LICENSE.md for details. -# - -digraph DeadReturnValuesOfFunctionCall { - NoOrDead; - Maybe [peripheries=2]; - Unknown; - - # Lattice - NoOrDead->Maybe; - Maybe->Unknown; - - # Transfer functions - Maybe->NoOrDead [label="Write"]; - Maybe->Unknown [label="Read"]; - Maybe->Unknown [label="UnknownFunctionCall"]; - Maybe->Unknown [label="TheCall"]; -} diff --git a/lib/StackAnalysis/Element.cpp b/lib/StackAnalysis/Element.cpp deleted file mode 100644 index 7ea507f3e..000000000 --- a/lib/StackAnalysis/Element.cpp +++ /dev/null @@ -1,339 +0,0 @@ -/// \file Element.cpp -/// \brief - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include "revng/Support/Debug.h" - -#include "Element.h" - -using llvm::Module; - -Logger<> SaDiffLog("sa-diff"); - -RunningStatistics AddressSpaceSizeStats("AddressSpaceSizeStats"); - -Logger<> SaVerboseLog("sa-verbose"); - -static size_t combineHash(size_t A, size_t B) { - return (A << 1 | A >> 31) ^ B; -} - -namespace StackAnalysis { - -size_t ASID::hash() const { - return std::hash()(ID); -} - -bool ASSlot::lowerThanOrEqual(const ASSlot &Other) const { - return cmp(Other, nullptr) == 0; -} - -template -unsigned ASSlot::cmp(const ASSlot &Other, const Module *M) const { - revng_assert(!this->isInvalid() and !Other.isInvalid()); - - LoggerIndent<> Y(SaDiffLog); - bool Result = not(AS.lowerThanOrEqual(Other.AS) && Offset == Other.Offset); - - if (SaDiffLog.isEnabled() && Result && Diff) { - Other.dump(M, SaDiffLog); - SaDiffLog << " does not contain "; - dump(M, SaDiffLog); - SaDiffLog << DoLog; - } - - return Result; -} - -size_t ASSlot::hash() const { - return combineHash(std::hash()(AS), std::hash()(Offset)); -} - -namespace Intraprocedural { - -bool Value::lowerThanOrEqual(const Value &Other) const { - return cmp(Other, nullptr) == 0; -} - -template -unsigned Value::cmp(const Value &Other, const Module *M) const { - LoggerIndent<> Y(SaDiffLog); - unsigned Result = 0; - - if (hasDirectContent() && Other.hasDirectContent()) { - // Force equality - // TODO: is this correct? shouldn't we assert DirectContent == - // Other.DirectContent? - ROA((DirectContent.cmp(Other.DirectContent, M)), - { revng_log(SaDiffLog, "DirectContent vs DirectContent"); }); - } - - // hasDirectContent() && !Other.hasDirectContent() is fine - - // Other has direct content and we don't, it's more specific than us - ROA(!hasDirectContent() && Other.hasDirectContent(), - { revng_log(SaDiffLog, "RHS has direct content, LHS doesn't"); }); - - // Losing the name is fine, acquiring it is not - ROA(!hasTag() && Other.hasTag(), - { revng_log(SaDiffLog, "RHS has tag, LHS doesn't"); }); - - ROA(hasTag() && Other.hasTag() && !TheTag.lowerThanOrEqual(Other.TheTag), - { revng_log(SaDiffLog, "Tag"); }); - - return Result; -} - -size_t Value::hash() const { - size_t Result = 0; - - Result = combineHash(Result, hasDirectContent()); - if (hasDirectContent()) - Result = combineHash(Result, std::hash()(*directContent())); - else - Result = combineHash(Result, Result); - - Result = combineHash(Result, hasTag()); - if (hasTag()) - Result = combineHash(Result, std::hash()(*tag())); - else - Result = combineHash(Result, Result); - - return Result; -} - -bool AddressSpace::lowerThanOrEqual(const AddressSpace &Other) const { - return cmp(Other, nullptr) == 0; -} - -template -unsigned AddressSpace::cmp(const AddressSpace &Other, const Module *M) const { - LoggerIndent<> Y(SaDiffLog); - unsigned Result = 0; - - for (auto &P : ASOContent) { - auto It = Other.ASOContent.find(P.first); - - // Check if Other has it - if (It != Other.ASOContent.end()) { - // Check the actual value - ROA((P.second.cmp(It->second, M)), { - slot(P.first).dump(M, SaDiffLog); - SaDiffLog << DoLog; - }); - } - } - - for (auto &P : Other.ASOContent) { - auto It = ASOContent.find(P.first); - // TODO: assert this matters in the PruneLog - ROA(It == ASOContent.end() && P.second.hasDirectContent(), { - slot(P.first).dump(M, SaDiffLog); - SaDiffLog << " is absent in the LHS and has direct content on the"; - revng_log(SaDiffLog, " RHS"); - }); - } - - return Result; -} - -size_t AddressSpace::hash() const { - size_t Result = 0; - - for (auto &P : ASOContent) { - Result = combineHash(Result, P.first); - Result = combineHash(Result, std::hash()(P.second)); - } - - return Result; -} - -std::set Element::collectSlots(int32_t CSVCount) const { - ASID CPU = ASID::cpuID(); - std::set SlotsPool; - - if (State.size() > CPU.id()) - for (auto &P : State[CPU.id()].ASOContent) - if (P.first < CSVCount) - SlotsPool.insert(ASSlot::create(CPU, P.first)); - - return SlotsPool; -} - -template unsigned -Element::cmp(const Element &Other, const Module *M) const; - -bool Element::lowerThanOrEqual(const Element &Other) const { - return cmp(Other, nullptr) == 0; -} - -template -unsigned Element::cmp(const Element &Other, const Module *M) const { - LoggerIndent<> Y(SaDiffLog); - unsigned Result = 0; - - if (Other.State.size() == 0) - return 0; - - if (State.size() == 0) - return 1; - - revng_assert(State.size() == Other.State.size()); - - size_t TotalASCount = State.size(); - for (unsigned I = 0; I < TotalASCount; I++) { - ROA((State[I].cmp(Other.State[I], M)), { - ASID(I).dump(SaDiffLog); - SaDiffLog << DoLog; - }); - } - - // TODO: we're ignoring FrameSizeAtCallSite and ABI - - return Result; -} - -size_t Element::hash() const { - size_t Result = 0; - for (const AddressSpace &AS : State) - Result = combineHash(Result, std::hash()(AS)); - return Result; -} - -Element &Element::combine(const Element &Other) { - if (isBottom()) { - *this = Other.copy(); - return *this; - } - - revng_assert(State.size() == Other.State.size()); - for (unsigned I = 0; I < State.size(); I++) - mergeASState(State[I], Other.State[I]); - - return *this; -} - -void Element::cleanup() { - for (AddressSpace &AS : State) { - for (auto It = AS.ASOContent.begin(); It != AS.ASOContent.end(); /**/) { - if (const ASSlot *TheTag = It->second.tag()) { - if (*TheTag == ASSlot::create(AS.ID, It->first)) { - It = AS.ASOContent.erase(It); - continue; - } - } - It++; - } - } -} - -void Element::apply(const Element &Other) { - revng_assert(State.size() == Other.State.size()); - - ASID CPU = ASID::cpuID(); - const AddressSpace &OtherCPU = Other.State[CPU.id()]; - for (auto &P : OtherCPU.ASOContent) - store(Value::fromSlot(CPU, P.first), P.second); -} - -std::set Element::computeCalleeSavedSlots() const { - std::set Result; - - // Look in the stack leftovers - uint32_t CPUID = ASID::cpuID().id(); - uint32_t StackID = ASID::stackID().id(); - if (State.size() > StackID and State.size() > CPUID) { - std::set StackLeftovers; - for (auto &P : State[StackID].ASOContent) { - // Do we have direct content with a name? - if (const ASSlot *T = P.second.tag()) { - // Is the tag referreing to a CSV? - if (T->addressSpace() == ASID::cpuID()) - StackLeftovers.insert(*T); - } - } - - for (auto &P : State[CPUID].ASOContent) { - // Do we have direct content with a name? - if (const ASSlot *T = P.second.tag()) { - // Is the name the same as the current slot? - ASSlot Slot = ASSlot::create(ASID::cpuID(), P.first); - if (*T == Slot and StackLeftovers.count(Slot) != 0) - Result.insert(Slot); - } - } - } - - return Result; -} - -void Element::mergeASState(AddressSpace &ThisState, - const AddressSpace &OtherState) { - // The following implementation can be easily replaced by any other - // implementation using a data structure allowing to iterate over a sorted - // pair of pairs. In particular, instead of a std::map we could use - // a sorted std::vector of pairs. - - // Iterate in parallel - auto ThisIt = ThisState.ASOContent.begin(); - auto ThisEndIt = ThisState.ASOContent.end(); - auto OtherIt = OtherState.ASOContent.begin(); - auto OtherEndIt = OtherState.ASOContent.end(); - std::vector> NewEntries; - - bool ThisDone = ThisIt == ThisEndIt; - bool OtherDone = OtherIt == OtherEndIt; - while (!ThisDone || !OtherDone) { - Value *ThisContent = nullptr; - const Value *OtherContent = nullptr; - Value TmpContent = Value::empty(); - - if (ThisDone || (!OtherDone && ThisIt->first > OtherIt->first)) { - // Only Other has the current offset: create a new default entry for - // delayed appending in this and merge it with OtherContent - auto ASO = ASSlot::create(ThisState.id(), OtherIt->first); - NewEntries.emplace_back(OtherIt->first, ThisState.load(ASO)); - - ThisContent = &NewEntries.back().second; - OtherContent = &OtherIt->second; - - OtherIt++; - } else if (OtherDone || (!ThisDone && OtherIt->first > ThisIt->first)) { - // Only this has the current offset: create a default OtherContent and - // merge with ThisContent - auto ASO = ASSlot::create(OtherState.id(), ThisIt->first); - TmpContent = OtherState.load(ASO); - - ThisContent = &ThisIt->second; - OtherContent = &TmpContent; - - ThisIt++; - } else { - // Both have the current offset: update ThisContent with OtherContent - revng_assert(ThisIt != ThisEndIt && OtherIt != OtherEndIt); - revng_assert(ThisIt->first == OtherIt->first); - - ThisContent = &ThisIt->second; - OtherContent = &OtherIt->second; - - ThisIt++; - OtherIt++; - } - - // Perform the merge - ThisContent->combine(*OtherContent); - - ThisDone = ThisIt == ThisEndIt; - OtherDone = OtherIt == OtherEndIt; - } - - for (std::pair &P : NewEntries) - ThisState.ASOContent[P.first] = P.second; -} - -} // namespace Intraprocedural - -} // namespace StackAnalysis diff --git a/lib/StackAnalysis/Element.h b/lib/StackAnalysis/Element.h deleted file mode 100644 index 05886e996..000000000 --- a/lib/StackAnalysis/Element.h +++ /dev/null @@ -1,429 +0,0 @@ -#pragma once - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include - -#include "revng/ADT/LazySmallBitVector.h" -#include "revng/Support/Statistics.h" - -#include "ASSlot.h" -#include "BasicBlockInstructionPair.h" - -/// \brief Average number of slots tracked by an address space -extern RunningStatistics AddressSpaceSizeStats; - -extern Logger<> SaVerboseLog; - -namespace StackAnalysis { - -namespace Intraprocedural { - -/// \brief A Value represents the value associated by the analysis to an SSA -/// value/slot -/// -/// A Value tracks two things: the actual content of an SSA value in a certain -/// moment (according to the expressive power of our analysis) and/or a "tag", -/// i.e., the fact that this value contains the value that the ASSlot associated -/// to the tag contained at function entry. This useful, e.g., to detect -/// callee-saved registers or if an indirect jump is targeting the value saved -/// in the link register. -class Value { -private: - ASSlot DirectContent; - ASSlot TheTag; - -public: - Value() : DirectContent(ASSlot::invalid()), TheTag(ASSlot::invalid()) {} - - static Value empty() { return Value(); } - - static Value fromSlot(ASSlot Slot) { - Value Result; - Result.DirectContent = Slot; - Result.TheTag = ASSlot::invalid(); - return Result; - } - - static Value fromSlot(ASID ID, int32_t Offset) { - return fromSlot(ASSlot::create(ID, Offset)); - } - - static Value fromTag(ASSlot TheTag) { - Value Result; - Result.DirectContent = ASSlot::invalid(); - Result.TheTag = TheTag; - return Result; - } - -public: - bool hasDirectContent() const { return !DirectContent.isInvalid(); } - - bool hasTag() const { return not TheTag.isInvalid(); } - - bool isEmpty() const { return not(hasDirectContent() || hasTag()); } - - bool operator==(const Value &Other) const { - return DirectContent == Other.DirectContent && TheTag == Other.TheTag; - } - - bool operator!=(const Value &Other) const { return !(*this == Other); } - - size_t hash() const; - - /// \brief Perform a comparison according to the analysis' lattice - bool lowerThanOrEqual(const Value &Other) const; - - template - unsigned cmp(const Value &Other, const llvm::Module *M) const; - - Value &combine(const Value &Other) { - // If direct content is different go to top (invalid) - if (DirectContent != Other.DirectContent) - DirectContent = ASSlot::invalid(); - - if (Other.TheTag.isInvalid() || TheTag != Other.TheTag) - TheTag = ASSlot::invalid(); - - return *this; - } - - const ASSlot *directContent() const { - if (DirectContent.isInvalid()) - return nullptr; - else - return &DirectContent; - } - - const ASSlot *tag() const { - if (TheTag.isInvalid()) - return nullptr; - else - return &TheTag; - } - - // TODO: Handle size of the offset - bool add(int32_t Addend) { - if (!hasDirectContent()) - return false; - - DirectContent.add(Addend); - return true; - } - - bool mask(uint64_t Operand) { - if (!hasDirectContent()) - return false; - - DirectContent.mask(Operand); - return true; - } - - void dump(const llvm::Module *M) const debug_function { dump(M, dbg); } - - template - void dump(const llvm::Module *M, T &Output) const { - if (hasTag()) - TheTag.dump(M, Output); - - if (hasDirectContent()) - DirectContent.dump(M, Output); - else - Output << " T"; - } -}; - -/// \brief Class representing the content of an address space -/// -/// An address space is composed by a set of pairs recording -/// what are the possible values of the slot at the given offset. -class AddressSpace { - friend class Element; - -public: - using Container = std::map; - -private: - /// Address space identifier - ASID ID; - /// Map associating an offset within the address space with a Value - Container ASOContent; - -public: - AddressSpace(ASID ID) : ID(ID) {} - - AddressSpace(const AddressSpace &) = default; - AddressSpace &operator=(const AddressSpace &) = default; - AddressSpace(AddressSpace &&) = default; - AddressSpace &operator=(AddressSpace &&) = default; - - ~AddressSpace() { AddressSpaceSizeStats.push(ASOContent.size()); } - - using ASOContentIt = Container::iterator; - ASOContentIt eraseASO(ASOContentIt It) { - revng_assert(!It->second.hasDirectContent()); - return ASOContent.erase(It); - } - - bool operator==(const AddressSpace &Other) const { - return ASOContent == Other.ASOContent; - } - - bool operator!=(const AddressSpace &Other) const { return !(*this == Other); } - - /// \brief Perform a comparison according to the analysis' lattice - bool lowerThanOrEqual(const AddressSpace &Other) const; - - template - unsigned cmp(const AddressSpace &Other, const llvm::Module *M) const; - - size_t hash() const; - - bool contains(int32_t Offset) const { return ASOContent.count(Offset) != 0; } - - void set(int32_t Offset, Value V) { ASOContent[Offset] = V; } - - ASID id() const { return ID; } - ASSlot slot(int32_t Offset) const { return ASSlot::create(ID, Offset); } - - Container::const_iterator begin() const { return ASOContent.begin(); } - Container::const_iterator end() const { return ASOContent.end(); } - - /// \brief Handle loading from a specific slot - Value load(ASSlot Address) const { - revng_assert(Address.addressSpace() == ID); - - // If we can load from it, return the result right away, otherwise return a - // value tagged with the requested address - if (const Value *LoadedASSlot = get(Address.offset())) { - return *LoadedASSlot; - } else { - // We're loading from a specific location in TargetAS, but we have no - // recorded information about that location - return Value::fromTag(Address); - } - } - - /// \brief Return the number of slots available in this state - size_t size() const { return ASOContent.size(); } - - bool verify(ASID StateID) const { return StateID == ID; } - - void dump(const llvm::Module *M) const debug_function { dump(M, dbg); } - - template - void dump(const llvm::Module *M, T &Output) const { - ID.dump(Output); - Output << ":"; - - for (auto &P : ASOContent) { - Output << "\n "; - ASSlot::dumpOffset(M, ID, P.first, Output); - Output << ": "; - P.second.dump(M, Output); - } - } - -private: - const Value *get(int32_t Offset) const { - auto It = ASOContent.find(Offset); - if (It == ASOContent.end()) - return nullptr; - else - return &It->second; - } -}; - -/// \brief Represents an element of the lattice of the stack analysis -/// -/// This class basically keeps the state of all the address spaces being -/// considered in the current analysis. -class Element { -public: - using Container = llvm::SmallVector; - -private: - // The following vector is indexed with ASID - Container State; - std::map> FrameSizeAtCallSite; - -private: - Element() {} - -public: - /// \brief Create a bottom element, which tracks nothing - static Element bottom() { return Element(); } - - /// \brief Create a regular element, which tracks the CPU and stack state - static Element initial() { - Element Result; - - unsigned Count = ASID::stackID().id() + 1; - Result.State.reserve(Count); - for (unsigned I = 0; I < Count; I++) - Result.State.emplace_back(ASID(I)); - - return Result; - } - - Element(const Element &Other) = delete; - Element &operator=(const Element &Other) = delete; - - Element(Element &&Other) = default; - Element &operator=(Element &&Other) = default; - - /// \note Copy constructor has been deleted, so that we don't accidentally - /// call it. Use this method instead. - Element copy() const { - Element Result; - Result.State = State; - Result.FrameSizeAtCallSite = FrameSizeAtCallSite; - return Result; - } - - bool operator==(const Element &Other) const { - // TODO: we're ignoring FrameSizeAtCallSite - return State == Other.State; - } - - bool operator!=(const Element &Other) const { return !(*this == Other); } - - /// \brief Perform a comparison according to the analysis' lattice - bool lowerThanOrEqual(const Element &Other) const; - - bool equal(const Element &RHS) const { - return this->lowerThanOrEqual(RHS) && RHS.lowerThanOrEqual(*this); - } - - size_t hash() const; - - /// \brief Performs a comparison with \p Other - /// - /// \tparam Diff should the differences be printed to dbg? - /// \tparam EarlyExit should the comparison stop at the first difference? - template - unsigned cmp(const Element &Other, const llvm::Module *M) const; - - bool isBottom() const { return State.size() == 0; } - - /// \brief Combine this lattice element with \p Other - Element &combine(const Element &Other); - - /// \brief Remove all the slots that say that they contain their initial value - void cleanup(); - - bool addressSpaceContainsTag(ASID AddressSpace, const ASSlot *TheTag) const { - for (auto &P : State[AddressSpace.id()].ASOContent) - if (P.second.hasTag() && *P.second.tag() == *TheTag) - return true; - - return false; - } - - /// \brief Apply to this context the given store log - void apply(const Element &StoreLog); - - std::set stackArguments(int32_t CallerStackSize) const { - std::set Result; - if (State.size() > 0) - for (auto &P : State[ASID::stackID().id()].ASOContent) - if (P.first >= 0) - Result.insert(P.first - CallerStackSize); - - return Result; - } - - /// \brief Update the element after a store of \p StoredValue has been - /// performed to \p Address - void store(Value Address, Value StoredValue) { - if (SaVerboseLog.isEnabled()) { - // TODO: get module - SaVerboseLog << "Storing "; - StoredValue.dump(nullptr, SaVerboseLog); - SaVerboseLog << " to "; - Address.dump(nullptr, SaVerboseLog); - SaVerboseLog << DoLog; - } - - // Does target have a direct component? - if (const ASSlot *AddressASO = Address.directContent()) { - ASID TargetASID = AddressASO->addressSpace(); - State[TargetASID.id()].set(AddressASO->offset(), StoredValue); - } - } - - /// \brief Return the content of \p TargetAddress according to this Element - Value load(const Value &TargetAddress) const { - // Does target have a direct component? - if (const ASSlot *ASO = TargetAddress.directContent()) - return State[ASO->addressSpace().id()].load(*ASO); - - return Value::empty(); - } - - /// \brief begin iterator for the states handled by this lattice element - Container::const_iterator begin() const { return State.begin(); } - Container::const_iterator end() const { return State.end(); } - - /// \brief Verify that this Element is coherent - bool verify() const { - unsigned ID = 0; - for (const AddressSpace &ASS : State) - if (not ASS.verify(ASID(ID++))) - return false; - - return true; - } - - void dump(const llvm::Module *M) const debug_function { dump(M, dbg); } - - template - void dump(const llvm::Module *M, T &Output) const { - for (const AddressSpace &ASS : State) { - ASS.dump(M, Output); - Output << "\n"; - } - } - - /// \brief Collect all the slots about which we have information - std::set collectSlots(int32_t CSVCount) const; - - /// \brief Identify the explicitly callee saved slots - std::set computeCalleeSavedSlots() const; - -private: - /// \brief Implement the combine for AddressSpace - void mergeASState(AddressSpace &ThisState, const AddressSpace &OtherState); -}; - -} // namespace Intraprocedural - -} // namespace StackAnalysis - -namespace std { - -template<> -struct hash { - size_t operator()(const StackAnalysis::Intraprocedural::Element &K) const { - return K.hash(); - } -}; - -template<> -struct hash { - size_t - operator()(const StackAnalysis::Intraprocedural::AddressSpace &K) const { - return K.hash(); - } -}; - -template<> -struct hash { - size_t operator()(const StackAnalysis::Intraprocedural::Value &K) const { - return K.hash(); - } -}; - -} // namespace std diff --git a/lib/StackAnalysis/FunctionABI.cpp b/lib/StackAnalysis/FunctionABI.cpp deleted file mode 100644 index 1de2dbb37..000000000 --- a/lib/StackAnalysis/FunctionABI.cpp +++ /dev/null @@ -1,1277 +0,0 @@ -/// \file FunctionABI.cpp -/// \brief Implementation of the ABI analysis - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include - -#include "llvm/ADT/SCCIterator.h" - -#include "revng/ADT/ZipMapIterator.h" -#include "revng/Support/GraphAlgorithms.h" -#include "revng/Support/MonotoneFramework.h" - -#include "ABIIR.h" -#include "FunctionABI.h" - -using std::conditional; -using std::tuple; -using std::tuple_element; -using std::tuple_size; - -using llvm::GraphTraits; -using llvm::make_range; -using llvm::Module; -using llvm::scc_iterator; - -using StackAnalysis::ABIIRBasicBlock; - -Logger<> SaABI("sa-abi"); - -namespace std { -template<> -struct iterator_traits> - : public scc_iterator_traits {}; -} // namespace std - -namespace StackAnalysis { - -using ABIIRBB = ABIIRBasicBlock; - -static ASID CPU = ASID::cpuID(); - -template -using MapOfMaps = DefaultMap, N1>; - -/// \brief A set of helper functions related to DefaultMap -namespace MapHelpers { - -enum Comparison { Lower = -1, Equal = 0, Greater = 1 }; - -/// \brief Similar to Rust cmp -template -static inline Comparison compare(T A, T B) { - return A == B ? Equal : (A < B ? Lower : Greater); -} - -template -unsigned -cmp(const DefaultMap &This, const DefaultMap &Other) { - LoggerIndent<> Y(SaDiffLog); - unsigned Result = 0; - - This.sort(); - Other.sort(); - - for (auto &P : zipmap_range(This, Other)) { - auto *ThisEntry = P.first; - auto *OtherEntry = P.second; - - if (ThisEntry != nullptr and OtherEntry != nullptr) { - ROA((ThisEntry->second.template cmp(OtherEntry->second)), - { revng_log(SaDiffLog, ThisEntry->first); }); - } else if (ThisEntry != nullptr) { - ROA((ThisEntry->second.template cmp(Other.getDefault())), - { revng_log(SaDiffLog, ThisEntry->first); }); - } else if (OtherEntry != nullptr) { - ROA((This.getDefault().template cmp(OtherEntry->second)), - { revng_log(SaDiffLog, OtherEntry->first); }); - } else { - revng_abort(); - } - } - - for (auto &P : This) { - ROA((P.second.template cmp(Other.getOrDefault(P.first))), - { revng_log(SaDiffLog, P.first); }); - } - - for (auto &P : Other) { - ROA((This.getOrDefault(P.first).template cmp(P.second)), - { revng_log(SaDiffLog, P.first); }); - } - - return Result; -} - -template -unsigned cmpWithModule(const DefaultMap &This, - const DefaultMap &Other, - ASID ID, - const Module *M) { - LoggerIndent<> Y(SaDiffLog); - unsigned Result = 0; - - This.sort(); - Other.sort(); - - for (auto &P : zipmap_range(This, Other)) { - auto *ThisEntry = P.first; - auto *OtherEntry = P.second; - - if (ThisEntry != nullptr and OtherEntry != nullptr) { - ROA((ThisEntry->second.template cmp(OtherEntry->second)), - { - ASSlot::create(ID, ThisEntry->first).dump(M, SaDiffLog); - SaDiffLog << DoLog; - }); - } else if (ThisEntry != nullptr) { - ROA((ThisEntry->second.template cmp(Other.getDefault())), - { - ASSlot::create(ID, ThisEntry->first).dump(M, SaDiffLog); - SaDiffLog << DoLog; - }); - } else if (OtherEntry != nullptr) { - ROA((This.getDefault().template cmp(OtherEntry->second)), - { - ASSlot::create(ID, OtherEntry->first).dump(M, SaDiffLog); - SaDiffLog << DoLog; - }); - } else { - revng_abort(); - } - } - - for (auto &P : This) { - ROA((P.second.template cmp(Other.getOrDefault(P.first))), { - ASSlot::create(ID, P.first).dump(M, SaDiffLog); - SaDiffLog << DoLog; - }); - } - - for (auto &P : Other) { - ROA((This.getOrDefault(P.first).template cmp(P.second)), { - ASSlot::create(ID, P.first).dump(M, SaDiffLog); - SaDiffLog << DoLog; - }); - } - - return Result; -} - -template -unsigned nestedCmpWithModule(const MapOfMaps &This, - const MapOfMaps &Other, - ASID ID, - const Module *M) { - LoggerIndent<> Y(SaDiffLog); - unsigned Result = 0; - - This.sort(); - Other.sort(); - - for (auto &P : zipmap_range(This, Other)) { - auto *ThisEntry = P.first; - auto *OtherEntry = P.second; - - if (ThisEntry != nullptr and OtherEntry != nullptr) { - ROA((cmpWithModule(ThisEntry->second, - OtherEntry->second, - ID, - M)), - { - ThisEntry->first.dump(SaDiffLog); - SaDiffLog << DoLog; - }); - } else if (ThisEntry != nullptr) { - ROA((cmpWithModule(ThisEntry->second, - Other.getDefault(), - ID, - M)), - { - ThisEntry->first.dump(SaDiffLog); - SaDiffLog << DoLog; - }); - } else if (OtherEntry != nullptr) { - ROA((cmpWithModule(This.getDefault(), - OtherEntry->second, - ID, - M)), - { - OtherEntry->first.dump(SaDiffLog); - SaDiffLog << DoLog; - }); - } else { - revng_abort(); - } - } - - for (auto &P : This) { - ROA((cmpWithModule(P.second, - Other.getOrDefault(P.first), - ID, - M)), - { - P.first.dump(SaDiffLog); - SaDiffLog << DoLog; - }); - } - - for (auto &P : Other) { - ROA((cmpWithModule(This.getOrDefault(P.first), - P.second, - ID, - M)), - { - P.first.dump(SaDiffLog); - SaDiffLog << DoLog; - }); - } - - return Result; -} - -template -static void combine(V &This, const Q &Other) { - This.combine(Other); -} - -template -static void -combine(DefaultMap &This, const DefaultMap &Other) { - - // TODO: use zipmap_range - - This.sort(); - Other.sort(); - llvm::SmallVector *, N> Missing; - auto ThisIt = This.begin(); - auto ThisEnd = This.end(); - auto OtherIt = Other.begin(); - auto OtherEnd = Other.end(); - - // Iterate over the two maps pairwise - while (OtherIt != OtherEnd && ThisIt != ThisEnd) { - switch (compare(ThisIt->first, OtherIt->first)) { - case Greater: - // Missing, add later (can't change This while iterating) - Missing.push_back(&*OtherIt); - OtherIt++; - break; - case Equal: - // Merge - combine(ThisIt->second, OtherIt->second); - ThisIt++; - OtherIt++; - break; - case Lower: - // Only ours, merge with default - combine(ThisIt->second, Other.Default); - ThisIt++; - break; - } - } - - // Handle the remaining elements of Other - while (OtherIt != OtherEnd) { - combine(This[OtherIt->first], OtherIt->second); - OtherIt++; - } - - // Handle the remaining elements of This - while (ThisIt != ThisEnd) { - combine(ThisIt->second, Other.Default); - ThisIt++; - } - - // Handle the elements we registered - for (auto *P : Missing) - combine(This[P->first], P->second); - - combine(This.Default, Other.Default); -} - -template -inline void dump(const Module *M, - T &Output, - const DefaultMap &D, - ASID ID, - const char *Prefix = "") { - std::string Longer(Prefix); - Longer += " "; - - Output << Prefix << "Default:\n"; - D.Default.dump(Output, Longer.data()); - Output << "\n"; - - for (auto &P : D) { - Output << Prefix; - ASSlot::create(ID, P.first).dump(M, Output); - Output << ":\n"; - P.second.dump(Output, Longer.data()); - Output << "\n"; - } -} - -template -inline void dump(const Module *M, - T &Output, - const MapOfMaps &D, - ASID ID, - const char *Prefix) { - std::string Longer(Prefix); - Longer += " "; - - Output << Prefix << "Default:\n"; - dump(M, Output, D.Default, ID, Longer.data()); - Output << "\n"; - - for (auto &P : D) { - Output << Prefix; - P.first.dump(Output); - Output << ":\n"; - dump(M, Output, P.second, ID, Longer.data()); - Output << "\n"; - } -} - -template -static void returnFromCall(V &This, const Q &Other) { - This.returnFromCall(Other); -} - -template -static void -returnFromCall(DefaultMap &This, const DefaultMap &Other) { - - This.sort(); - Other.sort(); - llvm::SmallVector *, N> Missing; - auto ThisIt = This.begin(); - auto ThisEnd = This.end(); - auto OtherIt = Other.begin(); - auto OtherEnd = Other.end(); - - // Iterate over the two maps pairwise - while (OtherIt != OtherEnd && ThisIt != ThisEnd) { - switch (compare(ThisIt->first, OtherIt->first)) { - case Greater: - // Missing, add later (can't change This while iterating) - Missing.push_back(&*OtherIt); - OtherIt++; - break; - case Equal: - // Merge - returnFromCall(ThisIt->second, OtherIt->second); - ThisIt++; - OtherIt++; - break; - case Lower: - // Only ours, merge with default - returnFromCall(ThisIt->second, Other.Default); - ThisIt++; - break; - } - } - - // Handle the remaining elements of Other - while (OtherIt != OtherEnd) { - returnFromCall(This[OtherIt->first], OtherIt->second); - OtherIt++; - } - - // Handle the remaining elements of This - while (ThisIt != ThisEnd) { - returnFromCall(ThisIt->second, Other.Default); - ThisIt++; - } - - // Handle the elements we registered - for (auto *P : Missing) - returnFromCall(This[P->first], P->second); - - returnFromCall(This.Default, Other.Default); -} - -template -void unknownFunctionCall(DefaultMap &This) { - This.Default.unknownFunctionCall(); - for (auto &P : This) - P.second.unknownFunctionCall(); -} - -template -void disable(DefaultMap &This) { - This.Default.disable(); - for (auto &P : This) - P.second.disable(); -} - -template -void enable(DefaultMap &This) { - This.Default.enable(); - for (auto &P : This) - P.second.enable(); -} - -} // namespace MapHelpers - -/// \brief Wrapper for an analysis that can inhibit it -template -class Inhibitor : public S { -public: - using Base = S; - -public: - bool Enabled; - -public: - Inhibitor() : S(), Enabled(false) {} - explicit Inhibitor(typename S::Values V) : S(V), Enabled(false) {} - explicit Inhibitor(typename S::Values V, bool Enabled) : - S(V), Enabled(Enabled) {} - - bool isEnabled() const { return Enabled; } - - void enable() { Enabled = true; } - void disable() { Enabled = false; } - - void combine(const Inhibitor &Other) { - // TODO: we should assert the non-enabled one is bottom, or just ignore it - S::combine(Other); - Enabled = Enabled || Other.Enabled; - } - - bool lowerThanOrEqual(const Inhibitor &Other) const { - if (isEnabled() and not Other.isEnabled()) - return false; - else - return S::lowerThanOrEqual(Other); - } - - void transfer(typename S::TransferFunction T) { - if (isEnabled()) - S::transfer(T); - } - - void transfer(GeneralTransferFunction T) { - if (isEnabled()) - S::transfer(T); - } - - void dump() const { dump(dbg); } - - template - void dump(T &Output) const { - // If analysis is inhibited, simply wrap it in parenthesis - if (not isEnabled()) - Output << "("; - S::dump(Output); - if (not isEnabled()) - Output << ")"; - } -}; - -/// \brief Return whether a certain analysis should start from return labels -/// only -template -static constexpr bool isReturnOnly() { - return false; -} - -// Currently only URVOF is supposed to start from return points only -template<> -constexpr bool isReturnOnly() { - return true; -} - -/// \brief Recursive template class to apply certain methods on all the analyses -/// in Tuple -/// -/// This class has many template argument which are used only in certain -/// functions. This saves from partial function specialization and from having -/// on class per function. -/// -/// \tparam Tuple the tuple of analysis to use -/// \tparam T see dumpAnalysis -/// \tparam Diff see dumpAnalysis -/// \tparam EarlyExit see dumpAnalysis -/// \tparam NextIndex index of the tuple type, used for the recursion -template::value> -struct AnalysesWrapperHelpers { - - using Next = AnalysesWrapperHelpers; - static const size_t Index = NextIndex - 1; - using Type = typename tuple_element::type::Base; - - static typename tuple_element::type &get(Tuple &This) { - return std::get(This); - } - - static const typename tuple_element::type & - get(const Tuple &This) { - return std::get(This); - } - - static void initial(Tuple &This, bool IsReturn) { - bool Enable = isReturnOnly() ? IsReturn : true; - get(This) = Inhibitor(Type::initial(), Enable); - Next::initial(This, IsReturn); - } - - static void combine(Tuple &This, const Tuple &Other) { - get(This).combine(std::get(Other)); - Next::combine(This, Other); - } - - // TODO: maybe we should call these "collect" - static void assign(RegisterState &This, const Tuple &Other) { - This.getByType() = std::get(Other); - Next::assign(This, Other); - } - - static void assign(CallSiteRegisterState &This, const Tuple &Other) { - This.getByType() = std::get(Other); - Next::assign(This, Other); - } - - static void disable(Tuple &This) { - get(This).disable(); - Next::disable(This); - } - - static void enable(Tuple &This) { - get(This).enable(); - Next::enable(This); - } - - static void transfer(Tuple &This, GeneralTransferFunction TF) { - get(This).transfer(TF); - Next::transfer(This, TF); - } - - static void dumpAnalysis(const Tuple &This, T &Output, const char *Prefix) { - StackAnalysis::dumpAnalysis(Output, Prefix, get(This)); - Next::dumpAnalysis(This, Output, Prefix); - } - - static void returnFromCall(Tuple &This, const RegisterState &Other) { - get(This).transfer(Other.getByType().returnTransferFunction()); - Next::returnFromCall(This, Other); - } - - static unsigned cmp(const Tuple &This, const Tuple &Other) { - unsigned Result = 0; - Result = !get(This).lowerThanOrEqual(std::get(Other)); - if (Result != 0) { - if (EarlyExit) - return Result; - - if (SaDiffLog.isEnabled() and Diff) { - SaDiffLog << Type::name() << ": "; - get(This).dump(SaDiffLog); - SaDiffLog << " and "; - std::get(Other).dump(SaDiffLog); - SaDiffLog << DoLog; - } - } - - return Result + Next::cmp(This, Other); - } -}; - -/// \brief Specialization for the base case (NextIndex == 0) -template -struct AnalysesWrapperHelpers { - static void initial(Tuple &, bool) {} - static void assign(Tuple &, const Tuple &) {} - static void combine(Tuple &, const Tuple &) {} - static void assign(RegisterState &, const Tuple &) {} - static void assign(CallSiteRegisterState &, const Tuple &) {} - static void disable(Tuple &) {} - static void enable(Tuple &) {} - static void transfer(Tuple &, GeneralTransferFunction) {} - static void dumpAnalysis(const Tuple &, T &, const char *) {} - static void returnFromCall(Tuple &, const RegisterState &) {} - static unsigned cmp(const Tuple &, const Tuple &) { return 0; } -}; - -/// \brief Helper class to dispatch methods required by Element onto the -/// low-level analyses -template -class AnalysesWrapper { - friend class RegisterState; - friend class CallSiteRegisterState; - -public: - Tuple Analyses; - -private: - using H = AnalysesWrapperHelpers; - using AnalysesType = Tuple; - -public: - static AnalysesWrapper initial(bool IsReturn) { - AnalysesWrapper Result; - H::initial(Result.Analyses, IsReturn); - return Result; - } - - AnalysesWrapper &combine(const AnalysesWrapper &Other) { - H::combine(this->Analyses, Other.Analyses); - return *this; - } - - void disable() { H::disable(this->Analyses); } - void enable() { H::enable(this->Analyses); } - - void write() { H::transfer(this->Analyses, GeneralTransferFunction::Write); } - - void read() { H::transfer(this->Analyses, GeneralTransferFunction::Read); } - - void unknownFunctionCall() { - H::transfer(this->Analyses, GeneralTransferFunction::UnknownFunctionCall); - } - - void returnFromCall(const RegisterState &Other) { - H::returnFromCall(this->Analyses, Other); - } - - template - unsigned cmp(const AnalysesWrapper &Other) const { - using H = AnalysesWrapperHelpers; - LoggerIndent<> Y(SaDiffLog); - return H::cmp(this->Analyses, Other.Analyses); - } - - void dump() const debug_function { dump(dbg); } - - template - void dump(T &Output, const char *Prefix = " ") const { - using H = AnalysesWrapperHelpers; - H::dumpAnalysis(this->Analyses, Output, Prefix); - } -}; - -/// Namespace for the classes composing the monotone framework of the ABI -/// analysis (and helper classes) -namespace ABIAnalysis { - -/// \brief Element of the lattice of the monotone framework, tracks the result -/// of the various analysis for each label -/// -/// This class basically acts as a dispatcher of the various actions/transfer -/// functions towards the underlying analysis specified in Analyses -/// -/// \tparam Analyses an AnalysesList type listing all the function and funcion -/// call analysis to perform. -template -class Element { - friend class ::StackAnalysis::FunctionABI; - -private: - using AWF = AnalysesWrapper; - using AWFC = AnalysesWrapper; - -private: - /// Map tracking the status of registers from the point of view of the current - /// function - DefaultMap RegisterAnalyses; - - /// Map tracking the status of registers from the point of view of the each - /// function call - // TODO: We could have as well have a vector here, considering calls are - // relatively rare - MapOfMaps FunctionCallRegisterAnalyses; - -public: - Element() {} - - static Element bottom() { return Element(); } - - /// \brief Explicit copy constructor - Element copy() const { - Element Result; - Result.RegisterAnalyses = RegisterAnalyses; - Result.FunctionCallRegisterAnalyses = FunctionCallRegisterAnalyses; - return Result; - } - - Element(const Element &) = delete; - Element &operator=(const Element &) = delete; - - Element(Element &&) = default; - Element &operator=(Element &&) = default; - -public: - /// Reset and enable all the function analyses - /// - /// This function enables all the function analyses except those that need to - /// start from a return basic block. In such cases, the analysis is enabled - /// only if \p IsReturn is true. - /// - /// \param IsReturn whether the current block is a return basic block or not - void resetFunctionAnalyses(bool IsReturn) { - RegisterAnalyses.clear(AWF::initial(IsReturn)); - } - - /// \brief Enable all the function call analyses associated to \p TheCall - void resetFunctionCallAnalyses(FunctionCall TheCall) { - MapHelpers::unknownFunctionCall(FunctionCallRegisterAnalyses[TheCall]); - MapHelpers::enable(FunctionCallRegisterAnalyses[TheCall]); - FunctionCallRegisterAnalyses[TheCall].clear(AWFC::initial(true)); - } - - bool lowerThanOrEqual(const Element &Other) const { - return cmp(Other) == 0; - } - - // TODO: review - template - unsigned cmp(const Element &Other, const Module *M = nullptr) const { - using namespace MapHelpers; - LoggerIndent<> Y(SaDiffLog); - unsigned Result = 0; - - auto registerCmp = cmpWithModule; - - ROA((registerCmp(RegisterAnalyses, Other.RegisterAnalyses, CPU, M)), - { revng_log(SaDiffLog, "RegisterAnalyses"); }); - - auto X = nestedCmpWithModule; - ROA((X(FunctionCallRegisterAnalyses, - Other.FunctionCallRegisterAnalyses, - CPU, - M)), - { revng_log(SaDiffLog, "RegisterAnalyses"); }); - - return Result; - } - - Element &combine(const Element &Other) { - MapHelpers::combine(RegisterAnalyses, Other.RegisterAnalyses); - MapHelpers::combine(FunctionCallRegisterAnalyses, - Other.FunctionCallRegisterAnalyses); - return *this; - } - - /// \brief Record that \p Slot has been written - void write(ASSlot Slot) { - // It should touch the slot at the given offset plus the slot in all the - // function call analyses, including default. - - if (Slot.addressSpace() == CPU) { - RegisterAnalyses[Slot.offset()].write(); - FunctionCallRegisterAnalyses.Default[Slot.offset()].write(); - for (auto &P : FunctionCallRegisterAnalyses) - P.second[Slot.offset()].write(); - } - } - - /// \brief Record that \p Slot has been read - void read(ASSlot Slot) { - // It should touch the slot at the given offset plus the slot in all the - // function call analyses, including default. - - if (Slot.addressSpace() == CPU) { - RegisterAnalyses[Slot.offset()].read(); - FunctionCallRegisterAnalyses.Default[Slot.offset()].read(); - for (auto &P : FunctionCallRegisterAnalyses) - P.second[Slot.offset()].read(); - } - } - - /// \brief Handle a call to a function for which the ABI analysis produced - /// \p Other - void directCall(const FunctionABI &CalleeABI) { - // It should touch all the register/stack slots plus all the register of - // every function call (including default). - - // All register analyses - MapHelpers::returnFromCall(RegisterAnalyses, CalleeABI.RegisterAnalyses); - - // All the register analyses of all the function calls (including default) - MapHelpers::returnFromCall(FunctionCallRegisterAnalyses.Default, - CalleeABI.RegisterAnalyses); - for (auto &P : FunctionCallRegisterAnalyses) - MapHelpers::returnFromCall(P.second, CalleeABI.RegisterAnalyses); - } - - void indirectCall() { - // It should touch all the register plus all the register/stack slots of - // every function call (including default). - - // All register analyses - MapHelpers::unknownFunctionCall(RegisterAnalyses); - - // All the register analyses of all the function calls (including default) - MapHelpers::unknownFunctionCall(FunctionCallRegisterAnalyses.Default); - for (auto &P : FunctionCallRegisterAnalyses) - MapHelpers::unknownFunctionCall(P.second); - } - - void dump(const Module *M, const char *Prefix = "") const debug_function { - dump(M, dbg, Prefix); - } - - template - void dump(const Module *M, T &Output, const char *Prefix = "") const { - std::stringstream Stream; - dumpInternal(M, Stream, Prefix); - Output << Stream.str(); - } - -private: - void dumpInternal(const Module *M, - std::stringstream &Output, - const char *Prefix = "") const { - MapHelpers::dump(M, Output, RegisterAnalyses, CPU, Prefix); - MapHelpers::dump(M, - Output, - FunctionCallRegisterAnalyses, - CPU, - (llvm::Twine(Prefix) + " ").str().c_str()); - } -}; - -/// \brief Given a tuple, produce a new tuple where each element is wrapped in -/// another template class -/// -/// \tparam Wrapper the template class to use for wrapping the elements of the -/// tuple. -/// \tparam Tuple the tuple to wrap. -template class Wrapper, - typename Tuple, - int I = tuple_size::value, - typename... Types> -class WrapIn { -public: - /// The resulting tuple - using Wrapped = Wrapper::type>; - using type = typename WrapIn::type; -}; - -template class Wrapper, typename Tuple, typename... Types> -class WrapIn { -public: - using type = std::tuple; -}; - -/// \brief Compile-time container for a set of function and function call -/// analyses -/// -/// \tparam A tuple of function analyses -/// \tparam A tuple of function call analyses -template -class AnalysesList { -public: - using Function = typename WrapIn::type; - using FunctionCall = typename WrapIn::type; -}; - -template -class Interrupt { -private: - enum Reason { Regular, Return, NoReturn, Summary }; - -private: - Reason TheReason; - Element Result; - -private: - explicit Interrupt(Reason TheReason, Element Result) : - TheReason(TheReason), Result(std::move(Result)) {} - - explicit Interrupt(Reason TheReason) : TheReason(TheReason), Result() {} - -public: - static Interrupt createRegular(Element Result) { - return Interrupt(Regular, std::move(Result)); - } - - static Interrupt createReturn(Element Result) { - return Interrupt(Return, std::move(Result)); - } - - static Interrupt createNoReturn() { return Interrupt(NoReturn); } - - static Interrupt createSummary(Element Result) { - return Interrupt(Summary, std::move(Result)); - } - -public: - bool requiresInterproceduralHandling() { - switch (TheReason) { - case Regular: - case Return: - return false; - case NoReturn: - case Summary: - return true; - } - - revng_abort(); - } - - bool isPartOfFinalResults() const { - revng_assert(TheReason == Regular or TheReason == Return); - return TheReason == Return; - } - - Element &&extractResult() { return std::move(Result); } -}; - -/// \brief The core of the ABI analysis -/// -/// This monotone framework implements the ABI analysis. -/// -/// \tparam IsForward whether the analysis should be performed forward or not -/// \tparam E an AnalysesList type listing all the function and funcion call -/// analysis to perform. -/// -/// \note Don't reset and re-run this analysis -template -class Analysis - : public MonotoneFramework, - ABIIRBasicBlock *, - Element, - IsForward ? ReversePostOrder : PostOrder, - ABIIRBasicBlock::links_const_range, - Interrupt> { - -private: - using DirectedLabelRange = typename conditional::type; - -public: - using Base = MonotoneFramework, - ABIIRBasicBlock *, - Element, - IsForward ? ReversePostOrder : PostOrder, - ABIIRBasicBlock::links_const_range, - Interrupt>; - -private: - /// The entry basic block of the function - ABIIRBasicBlock *FunctionEntry; - - /// Counter for basic block visits, for statistical purposes - unsigned VisitsCount; - - /// Flag to prevent the analysis from being run more than once - bool FirstRun; - - const std::set *ExtraFinalStates; - -public: - Analysis(ABIIRBasicBlock *FunctionEntry, - const std::set *ExtraFinalStates) : - Base(FunctionEntry), - FunctionEntry(FunctionEntry), - VisitsCount(0), - FirstRun(true), - ExtraFinalStates(ExtraFinalStates) {} - -public: - void assertLowerThanOrEqual(const Element &A, const Element &B) const { - const Module *M = getModule(FunctionEntry->basicBlock()); - ::StackAnalysis::assertLowerThanOrEqual(A, B, M); - } - - /// \brief Prevent the analysis from running twice - void initialize() { - revng_assert(FirstRun, "The ABIAnalysis cannot be run twice"); - FirstRun = false; - Base::initialize(); - } - - void dumpFinalState() const {} - - llvm::Optional> handleEdge(const Element &Original, - ABIIRBasicBlock *Source, - ABIIRBasicBlock *Destination) const { - return llvm::Optional>(); - } - - ABIIRBasicBlock::links_const_range - successors(ABIIRBasicBlock *BB, Interrupt &) const { - return BB->next(); - } - - size_t successor_size(ABIIRBasicBlock *BB, Interrupt &) const { - return BB->next_size(); - } - - Interrupt createSummaryInterrupt() { - return Interrupt::createSummary(std::move(this->FinalResult)); - } - - Interrupt createNoReturnInterrupt() const { - return Interrupt::createNoReturn(); - } - - Element extremalValue(ABIIRBasicBlock *BB) const { - Element Result; - - // Initialize to `::initial()` and enable all the function-related - // analyses. Some of the backward analyses are available only if we're - // starting from a proper return. - Result.resetFunctionAnalyses(BB->isPartOfFinalResults()); - - return Result; - } - - unsigned visitsCount() const { return VisitsCount; } - - Interrupt transfer(ABIIRBasicBlock *BB) { - Element Result = this->State[BB].copy(); - const Module *M = getModule(BB->basicBlock()); - - if (SaABI.isEnabled()) { - SaABI << "Transfer function for " << BB->basicBlock() << "\n"; - SaABI << " ABIIRBasicBlock:\n"; - BB->dump(SaABI, M, " "); - SaABI << " Initial state: \n"; - Result.dump(M, SaABI, " "); - } - - ++VisitsCount; - - for (ABIIRInstruction &I : range(BB)) { - - // Result is Element - switch (I.opcode()) { - case ABIIRInstruction::Load: - Result.read(I.target()); - break; - - case ABIIRInstruction::Store: - Result.write(I.target()); - break; - - case ABIIRInstruction::DirectCall: - Result.directCall(I.abi()); - break; - - case ABIIRInstruction::IndirectCall: - Result.indirectCall(); - break; - } - - // Once we get to a function call, if it's the first time we meet it, its - // analyses are going to be disabled. Here we first activate the unknown - // function call transfer function (while it might still be disabled) and - // then we enable all the analyses. - if (I.opcode() == ABIIRInstruction::DirectCall - or I.opcode() == ABIIRInstruction::IndirectCall) { - Result.resetFunctionCallAnalyses(I.call()); - } - } - - if (SaABI.isEnabled()) { - SaABI << " Final state: \n"; - Result.dump(M, SaABI, " "); - SaABI << DoLog; - } - - // We don't check BB->isPartOfFinalResults() since there are basic blocks - // that have no successors but are not returns. And we want to consider - // those too, unlike what happens with the stack analysis, where we are - // interested in understanding what happens from the point of view of the - // caller (e.g., if a callee-saved register is not restored on a noreturn - // path, we don't care). - if ((IsForward and BB->successor_size() == 0) - or (not IsForward and BB->predecessor_size() == 0) - or (ExtraFinalStates != nullptr and ExtraFinalStates->count(BB) != 0)) - return Interrupt::createReturn(std::move(Result)); - else - return Interrupt::createRegular(std::move(Result)); - } - -private: - DirectedLabelRange range(ABIIRBasicBlock *BB) { - return instructionRange(BB); - } -}; - -} // namespace ABIAnalysis - -// -// FunctionaABI methods -// - -// TODO: test me -template -std::set findMaximalSimplePathTerminatorsOfExitlessSCCs(NodeTy Entry) { - using GT = llvm::GraphTraits; - using InverseGT = llvm::GraphTraits>; - - std::set Result; - - using NodesVector = std::vector; - for (const NodesVector &SCC : exitless_scc_range(Entry)) { - std::set SCCNodes; - SCCNodes.clear(); - for (NodeTy BB : SCC) - SCCNodes.insert(BB); - - // Identify all the entry points - llvm::SmallVector EntryPoints; - for (NodeTy BB : SCC) { - auto Predecessors = make_range(InverseGT::child_begin(BB), - InverseGT::child_end(BB)); - for (NodeTy Predecessor : Predecessors) { - if (SCCNodes.count(Predecessor) == 0) { - EntryPoints.push_back(BB); - break; - } - } - } - - std::set OnStack; - auto IsOnStack = [&OnStack](NodeTy Successor) { - return OnStack.count(Successor) != 0; - }; - - struct StackElement { - StackElement(NodeTy Node) : - Node(Node), Next(GT::child_begin(Node)), End(GT::child_end(Node)) {} - - NodeTy Node; - typename GT::ChildIteratorType Next; - const typename GT::ChildIteratorType End; - }; - std::stack Stack; - - for (NodeTy EntryPoint : EntryPoints) { - revng_assert(Stack.empty()); - Stack.emplace(EntryPoint); - - OnStack.clear(); - OnStack.insert(EntryPoint); - - while (not Stack.empty()) { - StackElement &Current = Stack.top(); - - if (Current.Next == Current.End) { - - // Check if all the successors are on the stack - auto Begin = GT::child_begin(Current.Node); - auto End = GT::child_end(Current.Node); - if (std::all_of(Begin, End, IsOnStack)) { - // OK, this is the terminator of a maximal simple path - Result.insert(Current.Node); - } - - // We're done with this node pop it - Stack.pop(); - OnStack.erase(Current.Node); - - } else { - - // We still have a successor to process - NodeTy Successor = *Current.Next; - Current.Next++; - - // Push the successor on the stack, unless it's already there - if (not IsOnStack(Successor)) { - Stack.emplace(Successor); - OnStack.insert(Successor); - } - } - } - } - - revng_assert(Result.size() > 0); - } - - return Result; -} - -void FunctionABI::analyze(const ABIFunction &TheFunction) { - using namespace ABIAnalysis; - ABIIRBasicBlock *E = TheFunction.entry(); - - auto InfiniteLoopsExits = findMaximalSimplePathTerminatorsOfExitlessSCCs(E); - - if (InfiniteLoopsExits.size() > 0 and SaABI.isEnabled()) { - SaABI << "The following simple path terminators have been found: "; - for (const ABIIRBasicBlock *BB : InfiniteLoopsExits) { - SaABI << getName(BB->basicBlock()) << " "; - } - SaABI << DoLog; - } - - { - revng_log(SaABI, "Running forward function analyses"); - - // List of the forward ABI analyses to perform - // Note: Among the function analyses we also have an instance of the - // function call analyses so that we can use them interproceduraly to - // simulate the inling of the called function. - using DRAOF = DeadRegisterArgumentsOfFunction; - using UAOF = UsedArgumentsOfFunction; - using URVOFC = UsedReturnValuesOfFunctionCall; - using DRVOFC = DeadReturnValuesOfFunctionCall; - using FunctionWise = tuple; - using FunctionCallWise = tuple; - using ForwardList = AnalysesList; - - Analysis ForwardFunctionAnalyses(E, &InfiniteLoopsExits); - - ForwardFunctionAnalyses.registerExtremal(E); - - ForwardFunctionAnalyses.initialize(); - Interrupt Result = ForwardFunctionAnalyses.run(); - - int Average = ForwardFunctionAnalyses.visitsCount() / TheFunction.size(); - revng_log(SaABI, - "Forward function analyses terminated: " - << ForwardFunctionAnalyses.visitsCount() << " visits performed" - << " on " << TheFunction.size() << " blocks (" - << "average: " << Average << ")."); - - this->combine(Result.extractResult()); - } - - { - revng_log(SaABI, - "Running backward function analyses (" - << TheFunction.finals_size() << " return points)"); - /// List of the backward ABI analyses to perform - using URVOF = UsedReturnValuesOfFunction; - using RAOFC = RegisterArgumentsOfFunctionCall; - using FunctionWise = tuple; - using FunctionCallWise = tuple; - using BackwardList = AnalysesList; - Analysis BackwardFunctionAnalyses(E, nullptr); - - for (ABIIRBasicBlock *FinalBB : TheFunction.finals()) - BackwardFunctionAnalyses.registerExtremal(FinalBB); - - for (ABIIRBasicBlock *FinalBB : InfiniteLoopsExits) - BackwardFunctionAnalyses.registerExtremal(FinalBB); - - BackwardFunctionAnalyses.initialize(); - Interrupt Result = BackwardFunctionAnalyses.run(); - this->combine(Result.extractResult()); - } -} - -void FunctionABI::dumpInternal(const Module *M, - std::stringstream &Output) const { - MapHelpers::dump(M, Output, RegisterAnalyses, CPU); - - Output << "Calls:\n\n"; - for (auto &P : Calls) { - Output << " "; - P.first.dump(Output); - Output << ":\n"; - MapHelpers::dump(M, Output, P.second.Registers, CPU, " "); - Output << "\n"; - } -} - -} // namespace StackAnalysis diff --git a/lib/StackAnalysis/FunctionABI.h b/lib/StackAnalysis/FunctionABI.h deleted file mode 100644 index e14e60e7e..000000000 --- a/lib/StackAnalysis/FunctionABI.h +++ /dev/null @@ -1,613 +0,0 @@ -#pragma once - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include - -#include "revng/ADT/SmallMap.h" -#include "revng/ADT/ZipMapIterator.h" -#include "revng/StackAnalysis/FunctionsSummary.h" -#include "revng/Support/Statistics.h" - -#include "ABIDataFlows.h" -#include "ASSlot.h" -#include "BasicBlockInstructionPair.h" - -extern Logger<> SaABI; - -/// \brief Average number of registers tracked by the ABI analysis -extern RunningStatistics ABIRegistersCountStats; - -/// \brief Map with an updatable default value -/// -/// This is a map that can be used to lazily handle K elements: proceed with -/// your processing using the Default member, then when K is met, record the -/// state of Default in the map and proceed. -template -class DefaultMap { -public: - // TODO: the size of the SmallMap needs to be fine tuned - using Container = SmallMap; - using const_iterator = typename Container::const_iterator; - using iterator = typename Container::iterator; - using key_type = K; - using pointer = typename Container::pointer; - using const_pointer = typename Container::const_pointer; - using value_type = typename Container::value_type; - using mapped_type = typename Container::mapped_type; - -public: - V Default; - -private: - Container M; - -public: - DefaultMap() : Default() {} - - DefaultMap(const DefaultMap &) = default; - DefaultMap &operator=(const DefaultMap &) = default; - - DefaultMap(DefaultMap &&) = default; - DefaultMap &operator=(DefaultMap &&) = default; - -public: - const V &getDefault() const { return Default; } - - void clear() { - Default = V(); - M.clear(); - } - - void clear(V NewDefault) { - Default = V(NewDefault); - M.clear(); - } - - void sort() const { M.sort(); } - - size_t size() const { return M.size(); } - - bool contains(K Key) const { return M.count(Key) != 0; } - - void erase(K Key) { M.erase(Key); } - - V &operator[](const K Key) { - return (*M.insert({ Key, Default }).first).second; - } - - const V &get(const K Key) const { - auto It = M.find(Key); - revng_assert(It != M.end()); - return It->second; - } - - const V &getOrDefault(const K Key) const { - auto It = M.find(Key); - if (It == M.end()) - return Default; - else - return It->second; - } - - const_iterator begin() const { return M.begin(); } - const_iterator end() const { return M.end(); } - - iterator begin() { return M.begin(); } - iterator end() { return M.end(); } -}; - -namespace StackAnalysis { - -// Forward declarations -namespace ABIAnalysis { -template -class Element; -} - -class ABIFunction; - -struct CombineHelper { - - /// \brief Combine with URAOF - template - static void combine(RegisterArgument &This, - UsedArgumentsOfFunction::Values V) { - revng_assert(!FunctionCall); - - if (V == UsedArgumentsOfFunction::Yes) { - switch (This.Value) { - case RegisterArgument::NoOrDead: - This.Value = RegisterArgument::Contradiction; - break; - case RegisterArgument::Maybe: - This.Value = RegisterArgument::Yes; - break; - case RegisterArgument::No: - // No comes from ECS and wins over everything - break; - case RegisterArgument::Dead: - case RegisterArgument::Yes: - case RegisterArgument::Contradiction: - revng_abort(); - } - } - } - - /// \brief Combine with DRAOF - template - static void combine(RegisterArgument &This, - DeadRegisterArgumentsOfFunction::Values V) { - revng_assert(not FunctionCall); - - if (V == DeadRegisterArgumentsOfFunction::NoOrDead) { - switch (This.Value) { - case RegisterArgument::Maybe: - This.Value = RegisterArgument::NoOrDead; - break; - case RegisterArgument::Yes: - This.Value = RegisterArgument::Contradiction; - break; - case RegisterArgument::No: - // No comes from ECS and wins over everything - break; - case RegisterArgument::NoOrDead: - case RegisterArgument::Dead: - case RegisterArgument::Contradiction: - revng_abort(); - } - } - } - - /// \brief Combine with RAOFC - template - static void combine(RegisterArgument &This, - RegisterArgumentsOfFunctionCall::Values V) { - revng_assert(FunctionCall); - - if (V == RegisterArgumentsOfFunctionCall::Yes) { - switch (This.Value) { - case RegisterArgument::NoOrDead: - This.Value = RegisterArgument::Dead; - break; - case RegisterArgument::Maybe: - This.Value = RegisterArgument::Yes; - break; - case RegisterArgument::Yes: - case RegisterArgument::Dead: - case RegisterArgument::Contradiction: - break; - case RegisterArgument::No: - // No comes from ECS and wins over everything - break; - } - } - } - - /// \brief Combine with URVOF - static void - combine(FunctionReturnValue &This, UsedReturnValuesOfFunction::Values V) { - if (V == UsedReturnValuesOfFunction::YesOrDead) { - switch (This.Value) { - case FunctionReturnValue::Maybe: - This.Value = FunctionReturnValue::YesOrDead; - break; - case FunctionReturnValue::No: - // No comes from ECS and wins over everything - break; - case FunctionReturnValue::YesOrDead: - case FunctionReturnValue::NoOrDead: - case FunctionReturnValue::Contradiction: - revng_abort(); - } - } - } - - /// \brief Combine with DRVOFC - static void combine(FunctionCallReturnValue &This, - DeadReturnValuesOfFunctionCall::Values V) { - if (V == DeadReturnValuesOfFunctionCall::NoOrDead) { - switch (This.Value) { - case FunctionCallReturnValue::Maybe: - This.Value = FunctionCallReturnValue::NoOrDead; - break; - case FunctionCallReturnValue::Yes: - This.Value = FunctionCallReturnValue::Dead; - break; - case FunctionCallReturnValue::No: - // No comes from ECS and wins over everything - break; - case FunctionCallReturnValue::Contradiction: - case FunctionCallReturnValue::Dead: - case FunctionCallReturnValue::NoOrDead: - case FunctionCallReturnValue::YesOrDead: - revng_abort(); - } - } - } - - // Combine with URVOFC - static void combine(FunctionCallReturnValue &This, - UsedReturnValuesOfFunctionCall::Values V) { - if (V == UsedReturnValuesOfFunctionCall::Yes) { - switch (This.Value) { - case FunctionCallReturnValue::Dead: - case FunctionCallReturnValue::NoOrDead: - This.Value = FunctionCallReturnValue::Contradiction; - break; - case FunctionCallReturnValue::Maybe: - case FunctionCallReturnValue::Yes: - This.Value = FunctionCallReturnValue::Yes; - break; - case FunctionCallReturnValue::No: - // No comes from ECS and wins over everything - break; - case FunctionCallReturnValue::Contradiction: - case FunctionCallReturnValue::YesOrDead: - revng_abort(); - } - } - } -}; - -template -inline void dumpAnalysis(T &Output, const char *Prefix, const V &Analysis) { - Output << Prefix << V::name() << ": "; - Analysis.dump(Output); - Output << "\n"; -} - -/// \brief State of a register in terms of being an argument or a return value -/// in a certain call site -class CallSiteRegisterState { -private: - RegisterArgumentsOfFunctionCall RAOFC; - UsedReturnValuesOfFunctionCall URVOFC; - DeadReturnValuesOfFunctionCall DRVOFC; - -public: - template - CallSiteRegisterState &assign(const T &Other) { - T::H::assign(*this, Other.Analyses); - return *this; - } - - template - CallSiteRegisterState &combine(const T &Other) { - T::H::combine(*this, Other.Analyses); - return *this; - } - - void resetToUnknown() { - RAOFC = decltype(RAOFC)::initial(); - RAOFC.transfer(GeneralTransferFunction::UnknownFunctionCall); - URVOFC = decltype(URVOFC)::initial(); - URVOFC.transfer(GeneralTransferFunction::UnknownFunctionCall); - DRVOFC = decltype(DRVOFC)::initial(); - DRVOFC.transfer(GeneralTransferFunction::UnknownFunctionCall); - } - - void applyResults(FunctionCallRegisterArgument &V) const { - CombineHelper::combine(V, RAOFC.value()); - } - - void applyResults(FunctionCallReturnValue &V) const { - CombineHelper::combine(V, DRVOFC.value()); - CombineHelper::combine(V, URVOFC.value()); - } - - void dump(const char *Prefix) const debug_function { dump(dbg, Prefix); } - - template - void dump(T &Output, const char *Prefix) const { - dumpAnalysis(Output, Prefix, RAOFC); - dumpAnalysis(Output, Prefix, URVOFC); - dumpAnalysis(Output, Prefix, DRVOFC); - } - -private: - template - friend struct AnalysesWrapperHelpers; - - template - T &getByType(); - - template - const T &getByType() const; -}; - -template<> -inline RegisterArgumentsOfFunctionCall &CallSiteRegisterState::getByType() { - return RAOFC; -} - -template<> -inline UsedReturnValuesOfFunctionCall &CallSiteRegisterState::getByType() { - return URVOFC; -} - -template<> -inline DeadReturnValuesOfFunctionCall &CallSiteRegisterState::getByType() { - return DRVOFC; -} - -template<> -inline const RegisterArgumentsOfFunctionCall & -CallSiteRegisterState::getByType() const { - return RAOFC; -} - -template<> -inline const UsedReturnValuesOfFunctionCall & -CallSiteRegisterState::getByType() const { - return URVOFC; -} - -template<> -inline const DeadReturnValuesOfFunctionCall & -CallSiteRegisterState::getByType() const { - return DRVOFC; -} - -/// \brief State of a register in terms of being an argument or a return value -class RegisterState { -private: - // Core analyses - DeadRegisterArgumentsOfFunction DRAOF; - UsedArgumentsOfFunction URAOF; - UsedReturnValuesOfFunction URVOF; - - // Function-call related analyses, done only for intrerprocedural reasons - UsedReturnValuesOfFunctionCall URVOFC; - DeadReturnValuesOfFunctionCall DRVOFC; - RegisterArgumentsOfFunctionCall RAOFC; - -public: - void resetToUnknown() { - DRAOF = decltype(DRAOF)::initial(); - DRAOF.transfer(GeneralTransferFunction::UnknownFunctionCall); - URAOF = decltype(URAOF)::initial(); - URAOF.transfer(GeneralTransferFunction::UnknownFunctionCall); - URVOF = decltype(URVOF)::initial(); - URVOF.transfer(GeneralTransferFunction::UnknownFunctionCall); - - URVOFC = decltype(URVOFC)::initial(); - URVOFC.transfer(GeneralTransferFunction::UnknownFunctionCall); - DRVOFC = decltype(DRVOFC)::initial(); - DRVOFC.transfer(GeneralTransferFunction::UnknownFunctionCall); - RAOFC = decltype(RAOFC)::initial(); - RAOFC.transfer(GeneralTransferFunction::UnknownFunctionCall); - } - - template - RegisterState &assign(const T &Other) { - T::H::assign(*this, Other.Analyses); - return *this; - } - - void applyResults(FunctionRegisterArgument &V) const { - CombineHelper::combine(V, URAOF.value()); - CombineHelper::combine(V, DRAOF.value()); - } - - void applyResults(FunctionReturnValue &V) const { - CombineHelper::combine(V, URVOF.value()); - } - - bool isArgument() const { - return URAOF.value() == UsedArgumentsOfFunction::Yes; - } - - bool isReturnValue() const { - return URVOF.value() == UsedReturnValuesOfFunction::YesOrDead; - } - - void dump() const debug_function { dump(dbg); } - - template - void dump(T &Output, const char *Prefix = "") const { - std::string Longer(Prefix); - Longer += " "; - Prefix = Longer.data(); - dumpAnalysis(Output, Prefix, DRAOF); - dumpAnalysis(Output, Prefix, URAOF); - dumpAnalysis(Output, Prefix, URVOF); - dumpAnalysis(Output, Prefix, URVOFC); - dumpAnalysis(Output, Prefix, DRVOFC); - dumpAnalysis(Output, Prefix, RAOFC); - } - -private: - template - friend struct AnalysesWrapperHelpers; - - template - T &getByType(); - - template - const T &getByType() const; -}; - -template<> -inline DeadRegisterArgumentsOfFunction &RegisterState::getByType() { - return DRAOF; -} - -template<> -inline UsedArgumentsOfFunction &RegisterState::getByType() { - return URAOF; -} - -template<> -inline UsedReturnValuesOfFunction &RegisterState::getByType() { - return URVOF; -} - -template<> -inline UsedReturnValuesOfFunctionCall &RegisterState::getByType() { - return URVOFC; -} - -template<> -inline DeadReturnValuesOfFunctionCall &RegisterState::getByType() { - return DRVOFC; -} - -template<> -inline RegisterArgumentsOfFunctionCall &RegisterState::getByType() { - return RAOFC; -} - -template<> -inline const DeadRegisterArgumentsOfFunction &RegisterState::getByType() const { - return DRAOF; -} - -template<> -inline const UsedArgumentsOfFunction &RegisterState::getByType() const { - return URAOF; -} - -template<> -inline const UsedReturnValuesOfFunction &RegisterState::getByType() const { - return URVOF; -} - -template<> -inline const UsedReturnValuesOfFunctionCall &RegisterState::getByType() const { - return URVOFC; -} - -template<> -inline const DeadReturnValuesOfFunctionCall &RegisterState::getByType() const { - return DRVOFC; -} - -template<> -inline const RegisterArgumentsOfFunctionCall &RegisterState::getByType() const { - return RAOFC; -} - -/// \brief Class to track the ABI, i.e., the status of a register as an -/// argument/return value -class FunctionABI { - template - friend class ABIAnalysis::Element; - -private: - struct CallsAnalyses { - DefaultMap Registers; - }; - -private: - DefaultMap RegisterAnalyses; - DefaultMap Calls; - -public: - FunctionABI() {} - - /// \brief Explicit copy constructor - FunctionABI copy() const { - FunctionABI Result; - Result.RegisterAnalyses = RegisterAnalyses; - Result.Calls = Calls; - return Result; - } - - FunctionABI(const FunctionABI &) = delete; - FunctionABI &operator=(const FunctionABI &) = delete; - - FunctionABI(FunctionABI &&) = default; - FunctionABI &operator=(FunctionABI &&) = default; - - ~FunctionABI() { ABIRegistersCountStats.push(RegisterAnalyses.size()); } - -public: - /// \brief Perform the ABI analysis - void analyze(const ABIFunction &TheFunction); - - template - void combine(const ABIAnalysis::Element &Other) { - for (auto &P : Other.RegisterAnalyses) - RegisterAnalyses[P.first].assign(P.second); - - for (auto &P : Other.FunctionCallRegisterAnalyses) - for (auto &Q : P.second) - Calls[P.first].Registers[Q.first].assign(Q.second); - } - - void drop(ASSlot Slot) { - if (Slot.addressSpace() == ASID::cpuID()) - RegisterAnalyses.erase(Slot.offset()); - else - revng_abort(); - } - - void resetToUnknown(ASSlot Slot) { - if (Slot.addressSpace() == ASID::cpuID()) - RegisterAnalyses[Slot.offset()].resetToUnknown(); - else - revng_abort(); - } - - void applyResults(FunctionRegisterArgument &V, int32_t Offset) const { - if (RegisterAnalyses.contains(Offset)) - RegisterAnalyses.get(Offset).applyResults(V); - } - - void applyResults(FunctionCallRegisterArgument &V, - FunctionCall Call, - int32_t Offset) const { - if (Calls.contains(Call)) - if (Calls.get(Call).Registers.contains(Offset)) - Calls.get(Call).Registers.get(Offset).applyResults(V); - } - - void applyResults(FunctionReturnValue &V, int32_t Offset) const { - if (RegisterAnalyses.contains(Offset)) - RegisterAnalyses.get(Offset).applyResults(V); - } - - void applyResults(FunctionCallReturnValue &V, - FunctionCall Call, - int32_t Offset) const { - if (Calls.contains(Call)) - if (Calls.get(Call).Registers.contains(Offset)) - Calls.get(Call).Registers.get(Offset).applyResults(V); - } - - /// \brief Collect all the slots involved in this instance - void collectLocalSlots(std::set &SlotsPool) const { - for (auto &P : RegisterAnalyses) - SlotsPool.insert(ASSlot::create(ASID::cpuID(), P.first)); - } - - std::pair, std::set> collectYesRegisters() const { - std::set Arguments; - std::set ReturnValues; - for (auto &P : RegisterAnalyses) { - if (P.second.isArgument()) - Arguments.insert(P.first); - if (P.second.isReturnValue()) - ReturnValues.insert(P.first); - } - - return { Arguments, ReturnValues }; - } - - void dump(const llvm::Module *M) const debug_function { dump(M, dbg); } - - template - void dump(const llvm::Module *M, T &Output) const { - std::stringstream Stream; - dumpInternal(M, Stream); - Output << Stream.str(); - } - -private: - void dumpInternal(const llvm::Module *M, std::stringstream &Output) const; -}; - -} // namespace StackAnalysis diff --git a/lib/StackAnalysis/FunctionsSummary.cpp b/lib/StackAnalysis/FunctionsSummary.cpp deleted file mode 100644 index c4554b78a..000000000 --- a/lib/StackAnalysis/FunctionsSummary.cpp +++ /dev/null @@ -1,449 +0,0 @@ -/// \file FunctionsSummary.cpp -/// \brief Implementation of the classes representing an argument/return value -/// in a register - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include "boost/icl/interval_set.hpp" - -#include "revng/StackAnalysis/FunctionsSummary.h" -#include "revng/Support/IRHelpers.h" - -#include "ASSlot.h" - -using llvm::BasicBlock; -using llvm::BlockAddress; -using llvm::CallInst; -using llvm::cast; -using llvm::cast_or_null; -using llvm::dyn_cast; -using llvm::dyn_cast_or_null; -using llvm::GlobalVariable; -using llvm::Instruction; -using llvm::MDNode; -using llvm::MDString; -using llvm::MDTuple; -using llvm::Metadata; -using llvm::Module; -using llvm::SmallVector; -using llvm::StringRef; -using llvm::User; - -namespace StackAnalysis { - -template class RegisterArgument; -template class RegisterArgument; - -using FRA = FunctionRegisterArgument; -using FCRA = FunctionCallRegisterArgument; - -template<> -void FRA::combine(const FCRA &Other) { - // TODO: we're handling this as a special case - if (Value == No || Other.Value == FunctionCallRegisterArgument::No) { - Value = No; - return; - } - - revng_assert(Other.Value == FunctionCallRegisterArgument::Maybe - || Other.Value == FunctionCallRegisterArgument::Yes); - - revng_assert(Value == NoOrDead || Value == Maybe || Value == Contradiction - || Value == Yes || Value == Dead); - - if (Other.Value == FunctionCallRegisterArgument::Yes) { - switch (Value) { - case NoOrDead: - Value = Dead; - break; - case Maybe: - Value = Yes; - break; - case Contradiction: - Value = Contradiction; - break; - case Yes: - Value = Yes; - break; - case Dead: - Value = Dead; - break; - case No: - revng_abort(); - } - } else { - switch (Value) { - case NoOrDead: - Value = NoOrDead; - break; - case Maybe: - Value = Maybe; - break; - case Contradiction: - Value = Contradiction; - break; - case Yes: - Value = Yes; - break; - case Dead: - Value = Dead; - break; - case No: - revng_abort(); - } - } -} - -template<> -void FCRA::combine(const FRA &Other) { - // TODO: we're handling this as a special case - if (Value == No || Other.Value == FunctionRegisterArgument::No) { - Value = No; - return; - } - - revng_assert(Value == Maybe || Value == Yes); - - revng_assert(Other.Value == FunctionRegisterArgument::NoOrDead - || Other.Value == FunctionRegisterArgument::Maybe - || Other.Value == FunctionRegisterArgument::Contradiction - || Other.Value == FunctionRegisterArgument::Yes); - - if (Value == Yes) { - switch (Other.Value) { - case FunctionRegisterArgument::NoOrDead: - Value = Dead; - break; - case FunctionRegisterArgument::Maybe: - Value = Yes; - break; - case FunctionRegisterArgument::Contradiction: - Value = Contradiction; - break; - case FunctionRegisterArgument::Yes: - Value = Yes; - break; - default: - revng_abort(); - } - } else { - switch (Other.Value) { - case FunctionRegisterArgument::NoOrDead: - Value = NoOrDead; - break; - case FunctionRegisterArgument::Maybe: - Value = Maybe; - break; - case FunctionRegisterArgument::Contradiction: - Value = Contradiction; - break; - case FunctionRegisterArgument::Yes: - Value = Yes; - break; - default: - revng_abort(); - } - } -} - -void FunctionReturnValue::combine(const FunctionCallReturnValue &Other) { - revng_abort(); -} - -void FunctionCallReturnValue::combine(const FunctionReturnValue &Other) { - // TODO: we're handling this as a special case - if (Value == No || Other.Value == FunctionReturnValue::No) { - Value = No; - return; - } - - // *this has seen only URVOF, which can only have Maybe or Yes value - revng_assert(Other.Value == FunctionReturnValue::Maybe - || Other.Value == FunctionReturnValue::YesOrDead); - - // Other is affected by URVOFC and DRVOFC, so that possible states are Maybe, - // NoOrDead, Yes and Contradiction - revng_assert(Value == Maybe || Value == NoOrDead || Value == Yes - || Value == Contradiction); - - if (Other.Value == FunctionReturnValue::YesOrDead) { - switch (Value) { - case Maybe: - Value = Yes; - break; - case NoOrDead: - Value = Dead; - break; - case Yes: - Value = Yes; - break; - case Contradiction: - Value = Contradiction; - break; - default: - revng_abort(); - } - } else { - switch (Value) { - case Maybe: - Value = Maybe; - break; - case NoOrDead: - Value = NoOrDead; - break; - case Yes: - Value = Yes; - break; - case Contradiction: - Value = Contradiction; - break; - default: - revng_abort(); - } - } -} - -template -static auto sort(const T &Range, const F &getKey) { - using pointer = decltype(&*Range.begin()); - std::vector Sorted; - - for (auto &E : Range) { - Sorted.push_back(&E); - } - - auto Comparator = [&getKey](pointer &LHS, pointer &RHS) { - return getKey(LHS) < getKey(RHS); - }; - - std::sort(Sorted.begin(), Sorted.end(), Comparator); - - return Sorted; -} - -template -static auto sortByCSVName(const T &Range) { - using pointer = decltype(&*Range.begin()); - auto SortKey = [](pointer P) { return P->first->getName(); }; - return sort(Range, SortKey); -} - -void FunctionsSummary::dumpInternal(const Module *M, - StreamWrapperBase &&Stream) const { - std::stringstream Output; - - // Register the range of addresses covered by each basic block - using interval_set = boost::icl::interval_set; - using interval = boost::icl::interval; - std::map Coverage; - for (User *U : M->getFunction("newpc")->users()) { - auto *Call = dyn_cast(U); - if (Call == nullptr) - continue; - - BasicBlock *BB = Call->getParent(); - auto Address = MetaAddress::fromConstant(Call->getOperand(0)); - uint64_t Size = getLimitedValue(Call->getOperand(1)); - revng_assert(Address.isValid() && Size > 0); - - Coverage[BB] += interval::right_open(Address, Address + Size); - } - - // Sort the functions by name, for extra determinism! - using Pair = std::pair; - std::vector SortedFunctions; - for (auto &P : Functions) - SortedFunctions.push_back({ P.first, &P.second }); - auto Compare = [](const Pair &A, const Pair &B) { - return getName(A.first) < getName(B.first); - }; - std::sort(SortedFunctions.begin(), SortedFunctions.end(), Compare); - - const char *FunctionDelimiter = ""; - Output << "["; - for (auto &P : SortedFunctions) { - Output << FunctionDelimiter << "\n {\n"; - BasicBlock *Entry = P.first; - const FunctionDescription &Function = *P.second; - - Output << " \"entry_point\": \""; - if (Entry != nullptr) - Output << getName(Entry); - Output << "\",\n"; - Output << " \"entry_point_address\": \""; - if (Entry != nullptr) - Output << std::hex << "0x" << getBasicBlockPC(Entry).address(); - Output << "\",\n"; - - Output << " \"jt-reasons\": ["; - if (Entry != nullptr) { - const char *JTReasonsDelimiter = ""; - Instruction *T = Entry->getTerminator(); - revng_assert(T != nullptr); - MDNode *Node = T->getMetadata("revng.jt.reasons"); - SmallVector Reasons; - if (auto *Tuple = cast_or_null(Node)) { - // Collect reasons - for (Metadata *ReasonMD : Tuple->operands()) - Reasons.push_back(cast(ReasonMD)->getString()); - - // Sort the output to make it more deterministic - std::sort(Reasons.begin(), Reasons.end()); - - // Print out - for (StringRef Reason : Reasons) { - Output << JTReasonsDelimiter << "\"" << Reason.data() << "\""; - JTReasonsDelimiter = ", "; - } - } - } - Output << "],\n"; - - Output << " \"type\": \"" << getName(Function.Type) << "\",\n"; - - interval_set FunctionCoverage; - - const char *BasicBlockDelimiter = ""; - Output << " \"basic_blocks\": ["; - - // Sort basic blocks by name - using Pair = std::pair; - std::vector SortedBasicBlocks; - for (const Pair &P : Function.BasicBlocks) - SortedBasicBlocks.push_back(&*Function.BasicBlocks.find(P.first)); - auto Compare = [](const Pair *P, const Pair *Q) { - return P->first->getName() < Q->first->getName(); - }; - std::sort(SortedBasicBlocks.begin(), SortedBasicBlocks.end(), Compare); - - for (const Pair *P : SortedBasicBlocks) { - BasicBlock *BB = P->first; - BranchType::Values Type = P->second; - const char *TypeName = BranchType::getName(Type); - Output << BasicBlockDelimiter; - Output << "{\"name\": \"" << getName(BB) << "\", "; - Output << "\"type\": \"" << TypeName << "\", "; - auto It = Coverage.find(BB); - if (It != Coverage.end()) { - const interval_set &IntervalSet = It->second; - FunctionCoverage += IntervalSet; - revng_assert(IntervalSet.iterative_size() == 1); - const auto &Range = *(IntervalSet.begin()); - Output << "\"start\": \""; - Output << std::hex << "0x" << Range.lower().address(); - Output << "\", \"end\": \""; - Output << std::hex << "0x" << Range.upper().address(); - Output << "\""; - } else { - Output << "\"start\": \"\", \"end\": \"\""; - } - Output << "}"; - BasicBlockDelimiter = ", "; - } - Output << "],\n"; - - Output << " \"slots\": ["; - const char *SlotDelimiter = ""; - for (auto *P : sortByCSVName(Function.RegisterSlots)) { - auto &[CSV, RD] = *P; - Output << SlotDelimiter; - Output << "{\"slot\": \"" << CSV->getName().data() << "\", "; - - Output << "\"argument\": \""; - RD.Argument.dump(Output); - Output << "\", "; - Output << "\"return_value\": \""; - RD.ReturnValue.dump(Output); - Output << "\"}"; - SlotDelimiter = ", "; - } - Output << "],\n"; - - Output << " \"clobbered\": ["; - const char *ClobberedDelimiter = ""; - for (const GlobalVariable *CSV : Function.ClobberedRegisters) { - Output << ClobberedDelimiter; - Output << "\"" << CSV->getName().data() << "\""; - ClobberedDelimiter = ", "; - } - Output << "],\n"; - - const char *CoverageDelimiter = ""; - Output << " \"coverage\": ["; - for (const auto &Range : FunctionCoverage) { - Output << CoverageDelimiter; - Output << "{"; - Output << "\"start\": \"" << std::hex << "0x" << Range.lower().address(); - Output << "\", "; - Output << "\"end\": \"" << std::hex << "0x" << Range.upper().address(); - Output << "\"}"; - CoverageDelimiter = ", "; - } - Output << "],\n"; - - const char *FunctionCallDelimiter = ""; - Output << " \"function_calls\": ["; - for (const CallSiteDescription &CallSite : Function.CallSites) { - Output << FunctionCallDelimiter << "\n"; - Output << " {\n"; - Output << " \"caller\": "; - Output << "\"" << getName(CallSite.Call) << "\",\n"; - Output << " \"callee\": "; - Output << "\"" << getName(CallSite.Callee) << "\",\n"; - // TODO: caller address - // TODO: callee address - Output << " \"slots\": ["; - const char *FunctionCallSlotsDelimiter = ""; - for (auto *P : sortByCSVName(CallSite.RegisterSlots)) { - auto &[CSV, RD] = *P; - Output << FunctionCallSlotsDelimiter; - Output << "{\"slot\": \"" << CSV->getName().data() << "\", "; - - Output << "\"argument\": \""; - RD.Argument.dump(Output); - Output << "\", "; - Output << "\"return_value\": \""; - RD.ReturnValue.dump(Output); - Output << "\"}"; - - FunctionCallSlotsDelimiter = ", "; - } - Output << "]\n"; - - Output << " }"; - FunctionCallDelimiter = ","; - } - Output << "\n ]\n"; - - Output << " }"; - FunctionDelimiter = ","; - - Stream.flush(Output); - } - Output << "\n]\n"; - - Stream.flush(Output); -} - -using CSD = FunctionsSummary::CallSiteDescription; -GlobalVariable * -CSD::isCompatibleWith(const FunctionDescription &Function) const { - std::set Slots; - for (auto &P : RegisterSlots) - Slots.insert(P.first); - for (auto &P : Function.RegisterSlots) - Slots.insert(P.first); - - for (GlobalVariable *CSV : Slots) { - FunctionCallRegisterDescription FCRD = getOrDefault(RegisterSlots, CSV); - FunctionRegisterDescription FRD = getOrDefault(Function.RegisterSlots, CSV); - if (not FCRD.isCompatibleWith(FRD)) - return CSV; - } - - return nullptr; -} - -} // namespace StackAnalysis diff --git a/lib/StackAnalysis/IncoherentCallsAnalysis.cpp b/lib/StackAnalysis/IncoherentCallsAnalysis.cpp deleted file mode 100644 index 5b512ba3c..000000000 --- a/lib/StackAnalysis/IncoherentCallsAnalysis.cpp +++ /dev/null @@ -1,241 +0,0 @@ -/// \file IncoherentCallsAnalysis.cpp -/// \brief Implementation of a simple analysis to identify incoherence among the -/// ABI analysis of a call site and of a callee - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include "revng/Support/MonotoneFramework.h" - -#include "ABIIR.h" - -using llvm::Module; - -static Logger<> ICALogger("incoherent-calls-analysis"); - -namespace StackAnalysis { - -// Specialize debug_cmp for UnionMonotoneSet -template -struct debug_cmp> { - static unsigned cmp(const UnionMonotoneSet &This, - const UnionMonotoneSet &Other, - const llvm::Module *M) { - return This.lowerThanOrEqual(Other) ? 0 : 1; - } -}; - -namespace IncoherentCallsAnalysis { - -using Element = UnionMonotoneSet; - -class Interrupt { -private: - enum Reason { Regular, Return, SpecialStart, NoReturn, Summary }; - -private: - /// Interrupt reason - Reason TheReason; - - /// Final result: the set of stack slots read from the caller - Element Result; - -private: - explicit Interrupt(Reason TheReason, Element Result) : - TheReason(TheReason), Result(std::move(Result)) {} - - explicit Interrupt(Reason TheReason) : TheReason(TheReason), Result() {} - -public: - static Interrupt createRegular(Element Result) { - return Interrupt(Regular, std::move(Result)); - } - - static Interrupt createReturn(Element Result) { - return Interrupt(Return, std::move(Result)); - } - - static Interrupt createNoReturn() { return Interrupt(NoReturn); } - - static Interrupt createSummary(Element Result) { - return Interrupt(Summary, std::move(Result)); - } - - bool requiresInterproceduralHandling() { - switch (TheReason) { - case Regular: - case SpecialStart: - case Return: - return false; - - case NoReturn: - case Summary: - return true; - } - - revng_abort(); - } - - Element &&extractResult() { return std::move(Result); } - - bool isPartOfFinalResults() const { return TheReason == Return; } -}; - -/// \brief Analysis that computes the set of stack slots used incoherently -/// -/// This (backward) analysis identifies stack slots that are used as stack -/// arguments in a function call, but are read (before a store) by the -/// caller. We consider these incoherent. -class Analysis : public MonotoneFramework { - -private: - using DirectedLabelRange = ABIIRBasicBlock::reverse_range; - -public: - using Base = MonotoneFramework; - -private: - ABIIRBasicBlock *FunctionEntry; - std::set RegularExtremals; - std::set Incoherent; - -public: - Analysis(ABIIRBasicBlock *FunctionEntry) : - Base(FunctionEntry), FunctionEntry(FunctionEntry) {} - -public: - void assertLowerThanOrEqual(const Element &A, const Element &B) const { - const Module *M = getModule(FunctionEntry->basicBlock()); - ::StackAnalysis::assertLowerThanOrEqual(A, B, M); - } - -public: - const std::set &incoherentCalls() { return Incoherent; } - - void dumpFinalState() const {} - - llvm::Optional handleEdge(const Element &Original, - ABIIRBasicBlock *Source, - ABIIRBasicBlock *Destination) const { - return llvm::Optional(); - } - - ABIIRBasicBlock::links_const_range - successors(ABIIRBasicBlock *BB, Interrupt &) const { - return BB->next(); - } - - size_t successor_size(ABIIRBasicBlock *BB, Interrupt &) const { - return BB->next_size(); - } - - Interrupt createSummaryInterrupt() { - return Interrupt::createSummary(std::move(this->FinalResult)); - } - - Interrupt createNoReturnInterrupt() const { - return Interrupt::createNoReturn(); - } - - Element extremalValue(ABIIRBasicBlock *) const { return Element(); } - - Interrupt transfer(ABIIRBasicBlock *BB) { - revng_log(SaABI, "Analyzing " << BB->basicBlock()); - Element Result = this->State[BB].copy(); - auto SP0 = ASID::stackID(); - - revng_log(ICALogger, "Analyzing " << BB->basicBlock()); - LoggerIndent<> I(ICALogger); - - for (ABIIRInstruction &I : range(BB)) { - - switch (I.opcode()) { - case ABIIRInstruction::Load: - // The last thing we know about this stack slot is that it has been read - if (I.target().addressSpace() == SP0) { - auto Offset = I.target().offset(); - revng_log(ICALogger, "Reading SP0+" << Offset); - Result.insert(Offset); - } - break; - - case ABIIRInstruction::Store: - // The last thing we know about this stack slot is that it has been - // written to - if (I.target().addressSpace() == SP0) { - auto Offset = I.target().offset(); - revng_log(ICALogger, "Writing SP0+" << Offset); - Result.drop(Offset); - } - break; - - case ABIIRInstruction::DirectCall: - // If a stack argument is read by the caller after a call but before a - // store, it's incoherent - if (Result.contains_any_of(I.stackArguments())) { - if (ICALogger.isEnabled()) { - ICALogger << "Function call "; - I.call().dump(ICALogger); - ICALogger << " in " << BB->basicBlock() << " is incoherent.\n"; - ICALogger << "Callee arguments:\n"; - for (const auto &Slot : I.stackArguments()) { - ICALogger << " SP0+" << Slot << "\n"; - } - ICALogger << DoLog; - } - Incoherent.insert(I.call()); - } - break; - - default: - break; - } - } - - // We don't really care about the final result in this case - if (BB->predecessor_size() == 0) - return Interrupt::createReturn(std::move(Result)); - else - return Interrupt::createRegular(std::move(Result)); - } - -private: - DirectedLabelRange range(ABIIRBasicBlock *BB) { - return instructionRange(BB); - } -}; - -} // namespace IncoherentCallsAnalysis - -std::set -computeIncoherentCalls(ABIIRBasicBlock *Entry, - std::vector &Extremals) { - using namespace IncoherentCallsAnalysis; - - revng_log(SaABI, "Checking coherency for stack arguments"); - Analysis BackwardFunctionAnalyses(Entry); - - for (ABIIRBasicBlock *Extremal : Extremals) - BackwardFunctionAnalyses.registerExtremal(Extremal); - - revng_log(ICALogger, "Analyzing " << Entry->basicBlock()); - LoggerIndent<> I(ICALogger); - - BackwardFunctionAnalyses.initialize(); - Interrupt Result = BackwardFunctionAnalyses.run(); - - return BackwardFunctionAnalyses.incoherentCalls(); -} - -} // namespace StackAnalysis diff --git a/lib/StackAnalysis/InterproceduralAnalysis.cpp b/lib/StackAnalysis/InterproceduralAnalysis.cpp deleted file mode 100644 index 6a59690c2..000000000 --- a/lib/StackAnalysis/InterproceduralAnalysis.cpp +++ /dev/null @@ -1,1017 +0,0 @@ -/// \file InterproceduralAnalysis.cpp -/// \brief Implementation of the interprocedural portion of the stack analysis - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include - -#include "revng/Support/Statistics.h" - -#include "Cache.h" -#include "InterproceduralAnalysis.h" - -using llvm::BasicBlock; -using llvm::GlobalVariable; -using llvm::Instruction; -using llvm::Module; -using llvm::Optional; -using llvm::StringRef; - -using time_point = std::chrono::steady_clock::time_point; -using StringIntCounter = CounterMap; - -Logger<> SaInterpLog("sa-interp"); - -/// \brief Logger for counting how much time is spent on a function -static StringIntCounter FunctionAnalysisTime("FunctionAnalysisTime"); - -/// \brief Logger for counting how many times a function is analyzed -static StringIntCounter FunctionAnalysisCount("FunctionAnalysisCount"); - -template -static uint64_t nanoseconds(T Span) { - return std::chrono::duration_cast(Span).count(); -} - -namespace StackAnalysis { - -void InterproceduralAnalysis::push(BasicBlock *Entry) { - InProgressFunctions.insert(Entry); - InProgress.emplace_back(Entry, TheCache, &GCBI, InProgressFunctions); - FunctionAnalysisCount.push(Entry->getName().str()); -} - -void InterproceduralAnalysis::run(BasicBlock *Entry, ResultsPool &Results) { - using IFS = IntraproceduralFunctionSummary; - - revng_assert(InProgress.size() == 0); - - Optional Cached = TheCache.get(Entry); - - // Has this function been analyzed already? If so, skip it. - if (Cached) - return; - - // Setup logger: each time we start a new intraprocedural analysis we indent - // the output - SaInterpLog.setIndentation(0); - revng_log(SaInterpLog, "Running interprocedural analysis on " << Entry); - - // Push the request function in the worklist - push(Entry); - - FunctionType::Values Type = FunctionType::Invalid; - auto Result = Intraprocedural::Interrupt::createInvalid(); - - // Loop over the worklist - do { - Type = FunctionType::Invalid; - - // Get an element from the stack (but don't pop yet) - Analysis &Current = InProgress.back(); - - SaInterpLog.setIndentation(InProgress.size()); - revng_log(SaInterpLog, - "Analyzing function " - << Current.entry() << " (size: " << Current.size() - << " BBs, MustHit: " << Current.cacheMustHit() << ")"); - SaInterpLog.indent(); - - time_point Begin = std::chrono::steady_clock::now(); - - // Run/continue the intraprocedural analysis - Result = Current.run(); - - time_point End = std::chrono::steady_clock::now(); - FunctionAnalysisTime.push(Current.entry()->getName().str(), - nanoseconds(End - Begin)); - - revng_assert(Result.requiresInterproceduralHandling()); - - switch (Result.type()) { - case BranchType::FakeFunction: - revng_log(SaInterpLog, Current.entry() << " is fake"); - - // It's a fake function, mark it as so and resume from the caller - TheCache.markAsFake(Current.entry()); - - // Set the function type in case this is the last in the worklist - Type = FunctionType::Fake; - - // If it was recursive, pop until the recurions root (excluded, for now) - if (const auto *Root = getRecursionRoot(Current.entry())) - popUntil(Root); - - // Go back to the caller of the fake function. - pop(); - - // We basically evicted a function call which was supposed to hit the - // cache. Reset the flag. - if (InProgress.size() > 0) - InProgress.back().resetCacheMustHit(); - - break; - - case BranchType::UnhandledCall: { - BasicBlock *Callee = Result.getCallee(); - revng_assert(Callee != nullptr); - - revng_log(SaInterpLog, - Current.entry() << " performs an unhandled call to " << Callee); - - // Is it recursive? - if (getRecursionRoot(Callee) != nullptr) { - if (SaInterpLog.isEnabled()) { - SaInterpLog << Callee << " is recursive. Call stack:"; - for (const Analysis &WorkItem : InProgress) - SaInterpLog << " " << WorkItem.entry(); - SaInterpLog << DoLog; - } - - // We now have to inject in the cache a temporary entry. - TheCache.update(Callee, IFS::bottom()); - - // Ensure it was inserted in the cache - revng_assert(TheCache.get(Callee)); - - // At this point the intraprocedural analysis will resume employing - // bottom for the recursive call. Then, once the analysis is done, the - // interprocedural part will detect that the result associated with it - // has changed (hopefully the result won't be bottom) and will run the - // analysis again until we're stable. - } else { - // Just a regular (uncached) function call, push it on the stack - push(Callee); - } - - break; - } - - case BranchType::NoReturnFunction: - case BranchType::RegularFunction: { - const IFS &Summary = Result.getFunctionSummary(); - - revng_log(SaInterpLog, "We have a summary for " << Current.entry()); - - bool MustReanalyze = false; - - // Are there function calls that lead to a contradiction? - const std::set &Offending = Current.incoherentFunctions(); - if (Offending.size() != 0) { - // If so, mark the called function as fake and re-analyze the caller - for (BasicBlock *Entry : Offending) { - revng_log(SaInterpLog, - Entry << " leads to contradiction, marking it as fake"); - - revng_assert(Current.entry() != Entry); - TheCache.markAsFake(Entry); - } - - MustReanalyze = true; - } else { - // OK, no contradictions - - // TODO: we should probably move the cleanup earlier on - // Perform some maintainance before recording in the cache - IFS SummaryForCache = Summary.copy(); - SummaryForCache.FinalState.cleanup(); - - // Let's register the result in the cache and check if we got any - // changes w.r.t. to the last time we analyzed this function - MustReanalyze = TheCache.update(Current.entry(), SummaryForCache); - - revng_assert(TheCache.get(Current.entry())); - } - - if (SaLog.isEnabled()) { - revng_log(SaLog, "FinalState for " << getName(Current.entry())); - Summary.dump(getModule(Entry), SaLog); - } - - if (MustReanalyze) { - revng_log(SaInterpLog, "Something has changed, let's reanalyze"); - - // Go back to the root of the recursion, if any - if (const auto *Root = getRecursionRoot(Current.entry())) - popUntil(Root); - - // Something changed, reset and re-run the analysis - Current.initialize(); - - } else { - - revng_log(SaInterpLog, - "No improvement over the last analysis, we're OK"); - - switch (Result.type()) { - case BranchType::NoReturnFunction: - revng_log(SaInterpLog, Current.entry() << " doesn't return"); - TheCache.markAsNoReturn(Current.entry()); - Type = FunctionType::NoReturn; - break; - - case BranchType::RegularFunction: - Type = FunctionType::Regular; - break; - - default: - revng_abort(); - } - - // We're done here, let's go up one position in the stack - pop(); - } - - } break; - - case BranchType::InstructionLocalCFG: - case BranchType::FunctionLocalCFG: - case BranchType::FakeFunctionCall: - case BranchType::FakeFunctionReturn: - case BranchType::HandledCall: - case BranchType::IndirectCall: - case BranchType::Return: - case BranchType::BrokenReturn: - case BranchType::IndirectTailCall: - case BranchType::LongJmp: - case BranchType::Killer: - case BranchType::Unreachable: - case BranchType::Invalid: - revng_abort("Unexpected branch type in interprocedural analysis"); - } - - } while (InProgress.size() > 0); - - revng_assert(Type != FunctionType::Invalid); -} - -void ResultsPool::mergeFunction(BasicBlock *Function, - const IntraproceduralFunctionSummary &Summary) { - using FRA = FunctionRegisterArgument; - using FRV = FunctionReturnValue; - using FCRA = FunctionCallRegisterArgument; - using FCRV = FunctionCallReturnValue; - - const Module *M = getModule(Function); - size_t CSVCount = std::distance(M->global_begin(), M->global_end()); - - LocallyWrittenRegisters[Function] = Summary.WrittenRegisters; - FakeReturns[Function] = Summary.FakeReturns; - - // Merge results from the arguments analyses - const FunctionABI &ABI = Summary.ABI; - auto &Slots = Summary.LocalSlots; - for (auto &Slot : Slots) { - int32_t Offset = Slot.first.offset(); - FunctionSlot Key = { Function, Offset }; - - revng_assert(Slot.first.addressSpace() == ASID::cpuID() - and Offset <= static_cast(CSVCount)); - - switch (Slot.second) { - case LocalSlotType::UsedRegister: - case LocalSlotType::ForwardedArgument: - case LocalSlotType::ForwardedReturnValue: - ABI.applyResults(FunctionRegisterArguments[Key], Offset); - ABI.applyResults(FunctionReturnValues[Key], Offset); - - // Handle forwarded arguments/return values (push rax; pop rdx) - if (Slot.second == LocalSlotType::ForwardedArgument - && FunctionRegisterArguments[Key].value() == FRA::Yes) { - FunctionRegisterArguments[Key] = FRA::maybe(); - } - - if (Slot.second == LocalSlotType::ForwardedReturnValue - && (FunctionReturnValues[Key].value() == FRV::YesOrDead)) { - FunctionReturnValues[Key] = FRV::maybe(); - } - - break; - - case LocalSlotType::ExplicitlyCalleeSavedRegister: - FunctionRegisterArguments[Key] = FRA::no(); - FunctionReturnValues[Key] = FRV::no(); - ExplicitlyCalleeSavedRegisters[Function].insert(Slot.first.offset()); - break; - } - - for (auto &P : CallSites) { - CallSite Call = P.first; - - if (!Call.belongsTo(Function)) - continue; - - FunctionCallSlot K = { Call, Offset }; - Instruction *I = Call.callInstruction(); - FunctionCall TheCall = { getFunctionCallCallee(I->getParent()), I }; - - switch (Slot.second) { - case LocalSlotType::UsedRegister: - case LocalSlotType::ForwardedArgument: - case LocalSlotType::ForwardedReturnValue: - case LocalSlotType::ExplicitlyCalleeSavedRegister: - - ABI.applyResults(FunctionCallRegisterArguments[K], TheCall, Offset); - ABI.applyResults(FunctionCallReturnValues[K], TheCall, Offset); - - // Handle forwarded arguments/return values (push rax; pop rdx) - if (Slot.second == LocalSlotType::ForwardedArgument - && FunctionCallRegisterArguments[K].value() == FCRA::Yes) { - FunctionCallRegisterArguments[K] = FCRA::maybe(); - } - - if (Slot.second == LocalSlotType::ForwardedReturnValue - && FunctionCallReturnValues[K].value() == FCRV::Yes) { - FunctionCallReturnValues[K] = FCRV::maybe(); - } - } - } - } -} - -void ResultsPool::mergeBranches(BasicBlock *Function, - const BasicBlockTypeMap &Branches) { - // Merge information about the branches type - for (auto &P : Branches) - BranchesType[{ Function, P.first->getTerminator() }] = P.second; -} - -void ResultsPool::mergeCallSites(BasicBlock *Entry, - const StackSizeMap &ToImport) { - for (auto &P : ToImport) { - FunctionCalls[Entry].push_back(P.first); - CallSite Call = { Entry, P.first.callInstruction() }; - auto It = CallSites.find(Call); - if (It != CallSites.end()) { - if (!compareOptional(It->second, P.second)) { - revng_abort("This call site has a stack at a different height than" - " previously recorded"); - } - } else { - CallSites[Call] = P.second; - } - } -} - -/// \brief Helper class for computing the set of registers clobbered by each -/// function -/// -/// This class basically takes each function, computes the set of all the -/// registers written by it and all of the functions in the transitive closure -/// of the callee set and remove the registers that are explicitly callee saved -/// (ECS). -/// -/// This process is repeated multiple times to handle with increasing precision -/// recursive and indirect function calls, which are initially ignored. After -/// the first iteration, the recursive function calls use the result from the -/// previous iteration. On the other hand, indirect function calls are -/// considered clobbering all the registers except those that are ECS in the -/// majority of the functions. -struct ClobberedRegistersAnalysis { - using ClobberedMap = std::map>; - - /// \brief Struct representing the result of a single iteration - struct IterationResult { - public: - /// \brief Description of a register - struct ECSVote { - public: - unsigned ECS; ///< Number of functions in which this register is ECS - unsigned Total; ///< Number of functions writing this register - - public: - bool operator==(const ECSVote &Other) const { - return std::tie(ECS, Total) == std::tie(Other.ECS, Other.Total); - } - - bool isECS() const { return ECS > Total / 2; } - }; - - public: - ClobberedMap Clobbered; - std::map ECSVotes; - - public: - bool operator==(const IterationResult &Other) const { - using std::tie; - return tie(Clobbered, ECSVotes) == tie(Other.Clobbered, Other.ECSVotes); - } - - bool operator!=(const IterationResult &Other) const { - return not(*this == Other); - } - }; - - /// \brief Compute an iteration - static IterationResult - recompute(ResultsPool &This, const ClobberedMap &InitialState) { - // The results pool - IterationResult Result; - auto &Clobbered = Result.Clobbered; - auto &ECSVotes = Result.ECSVotes; - - // Loop over all the functions - for (auto &P : This.FunctionTypes) { - BasicBlock *Function = P.first; - - // Have we handled this already? - if (Clobbered.count(Function) != 0) - continue; - - using iterator = typename std::vector::const_iterator; - - struct State { - public: - BasicBlock *Function; - iterator CallIt; - iterator EndCallIt; - - public: - State(BasicBlock *Function, iterator CallIt, iterator EndCallIt) : - Function(Function), CallIt(CallIt), EndCallIt(EndCallIt) {} - }; - - // Worklist - std::deque WorkList; - - // Set of currently in-progress functions, used to detect recursive - // function calls - std::set InProgress; - - // Initialize the worklist - const auto &FunctionCallsList = This.FunctionCalls[Function]; - WorkList.emplace_back(Function, - FunctionCallsList.begin(), - FunctionCallsList.end()); - InProgress.insert(Function); - - // Loop over the worklist - while (not WorkList.empty()) { - // Peek but don't pop - State &Current = WorkList.back(); - BasicBlock *Function = Current.Function; - - // Get a reference to the results for the current function - std::set &CurrentClobbered = Clobbered[Function]; - - // Loop over the unprocessed function calls - while (Current.CallIt != Current.EndCallIt) { - // Get the callee - BasicBlock *Callee = Current.CallIt->callee(); - - if ((Callee == nullptr) or (InProgress.count(Callee) != 0)) { - // Indirect or recursive function call, use result from last - // iteration - auto It = InitialState.find(Callee); - if (It != InitialState.end()) - CurrentClobbered.insert(It->second.begin(), It->second.end()); - - } else { - // Do we already handle this callee? - auto ClobberedIt = Clobbered.find(Callee); - if (ClobberedIt == Clobbered.end()) { - // No, push it on the worklist - const auto &FunctionCallsList = This.FunctionCalls[Callee]; - WorkList.emplace_back(Callee, - FunctionCallsList.begin(), - FunctionCallsList.end()); - InProgress.insert(Callee); - - // Early exit so we can proceed from the callee - break; - } - - // OK, we already processed this callee - - // Merge in the clobbered set all those clobbered by the callee - CurrentClobbered.insert(ClobberedIt->second.begin(), - ClobberedIt->second.begin()); - } - - // Proceed to the next call site - Current.CallIt++; - } - - // Are we done? - if (Current.CallIt == Current.EndCallIt) { - // Oh, we're done - - { - // Add all the locally written registers - auto It = This.LocallyWrittenRegisters.find(Function); - if (It != This.LocallyWrittenRegisters.end()) - CurrentClobbered.insert(It->second.begin(), It->second.end()); - } - - // Increase the counter associated to each written register - for (int32_t Index : CurrentClobbered) - ECSVotes[Index].Total++; - - { - // Erase from the clobbered registers all the callee-saved, if any - auto It = This.ExplicitlyCalleeSavedRegisters.find(Function); - if (It != This.ExplicitlyCalleeSavedRegisters.end()) { - // Do not use CurrentClobbered.erase(BeginIt, EndIt); - for (int32_t Index : It->second) - CurrentClobbered.erase(Index); - - // Increase the counter associated to each register - for (int32_t Index : It->second) - ECSVotes[Index].ECS++; - } - } - - // Pop from the worklist - WorkList.pop_back(); - InProgress.erase(Function); - } - } - } - - return Result; - } - - /// \brief Repeat the analysis until a fixed point is reached - static ClobberedMap run(ResultsPool &This) { - IterationResult LastResult; - IterationResult NewResult; - - do { - LastResult = std::move(NewResult); - - // Use as initial state the previous iteration's state - NewResult = recompute(This, LastResult.Clobbered); - - // Perform a majority vote on the result to associate to indirect function - // calls - std::set IndirectCallClobbered; - for (auto &P : NewResult.ECSVotes) { - - // When a register is written, is it usually an ECS? - if (not P.second.isECS()) { - // No, consider it clobbered by indirect function calls - IndirectCallClobbered.insert(P.first); - } - } - - // Assert the new set of registers clobbered by indirect function calls - // contains at least all of the registers clobbered in the previous - // iteration. If this is not the case, the algorithm might not converge. - for (int32_t Clobbered : LastResult.Clobbered[nullptr]) - revng_assert(IndirectCallClobbered.count(Clobbered) != 0); - - // Save the results of the vote as the result associated with nullptr - NewResult.Clobbered[nullptr] = std::move(IndirectCallClobbered); - - } while (LastResult != NewResult); - - return std::move(NewResult.Clobbered); - } -}; - -FunctionsSummary ResultsPool::finalize(Module *M, Cache *TheCache) { - ASID CPU = ASID::cpuID(); - - // Create the result data structure - FunctionsSummary Result; - - // Set function types - for (auto &P : FunctionTypes) - Result.Functions[P.first].Type = P.second; - - // Set function types - for (auto &P : FakeReturns) - Result.Functions[P.first].FakeReturns = P.second; - - // Compute the set of registers clobbered by each function - ClobberedRegistersAnalysis::ClobberedMap Clobbered; - Clobbered = ClobberedRegistersAnalysis::run(*this); - for (auto &P : Clobbered) { - auto &Function = Result.Functions[P.first]; - for (int32_t Offset : P.second) - Function.ClobberedRegisters.insert(TheCache->getCSVByIndex(Offset)); - } - - // Register block types - for (auto &P : BranchesType) { - BasicBlock *BB = P.first.branch()->getParent(); - Result.Functions[P.first.entry()].BasicBlocks[BB] = P.second; - } - - using CallSiteDescription = FunctionsSummary::CallSiteDescription; - - // - // Collect, for each call site, all the slots and create a CallSiteDescription - // - struct FunctionCallSites { - /// \brief Collect all the slots used by the function/its callers - std::set Slots; - /// \brief The callers - std::map CallSites; - }; - std::map FunctionCallSitesMap; - - auto &FCRA = FunctionCallRegisterArguments; - auto &FCRV = FunctionCallReturnValues; - auto &FRA = FunctionRegisterArguments; - auto &FRV = FunctionReturnValues; - - for (auto &P : FRA) { - BasicBlock *FunctionEntry = P.first.first; - FunctionCallSites &FCS = FunctionCallSitesMap[FunctionEntry]; - auto Slot = ASSlot::create(CPU, P.first.second); - FCS.Slots.insert(Slot); - } - - for (auto &P : FRV) { - BasicBlock *FunctionEntry = P.first.first; - FunctionCallSites &FCS = FunctionCallSitesMap[FunctionEntry]; - auto Slot = ASSlot::create(CPU, P.first.second); - FCS.Slots.insert(Slot); - } - - // Go over arguments of function calls - for (auto &P : FCRA) { - const CallSite &TheCallSite = P.first.first; - auto Slot = ASSlot::create(CPU, P.first.second); - BasicBlock *CallerBB = TheCallSite.callInstruction()->getParent(); - BasicBlock *Callee = getFunctionCallCallee(CallerBB); - FunctionCallSites &FCS = FunctionCallSitesMap[Callee]; - - // Register the slot - FCS.Slots.insert(Slot); - - // Check if we already created the CallSiteDescription - auto It = FCS.CallSites.find(TheCallSite); - if (It == FCS.CallSites.end()) { - auto &CallerCallSites = Result.Functions[TheCallSite.caller()].CallSites; - Instruction *I = TheCallSite.callInstruction(); - CallerCallSites.emplace_back(I, Callee); - FCS.CallSites[TheCallSite] = &CallerCallSites.back(); - revng_assert(FCS.CallSites[TheCallSite] == &CallerCallSites.back()); - } - } - - // Go over return values of function calls - for (auto &P : FCRV) { - const CallSite &TheCallSite = P.first.first; - auto Slot = ASSlot::create(CPU, P.first.second); - BasicBlock *CallerBB = TheCallSite.callInstruction()->getParent(); - BasicBlock *Callee = getFunctionCallCallee(CallerBB); - FunctionCallSites &FCS = FunctionCallSitesMap[Callee]; - FCS.Slots.insert(Slot); - } - - // - // Merge information about a function and all the call sites targeting it - // - - // For each function - for (auto &P : Result.Functions) { - BasicBlock *FunctionEntry = P.first; - - // Integrate slots from each call site - FunctionCallSites &FCS = FunctionCallSitesMap[FunctionEntry]; - - // Iterate over each slot - for (ASSlot Slot : FCS.Slots) { - revng_assert(Slot.addressSpace() == CPU); - int32_t Offset = Slot.offset(); - if (not TheCache->isCSVIndex(Offset)) - continue; - - GlobalVariable *CSV = TheCache->getCSVByIndex(Offset); - FunctionSlot TheFunctionSlot{ FunctionEntry, Offset }; - - bool CalleeHasSlot = FRA.count(TheFunctionSlot) != 0; - if (FunctionEntry == nullptr or not CalleeHasSlot) { - for (auto &Q : FCS.CallSites) { - CallSiteDescription &TheCallSiteDescription = *Q.second; - auto &CallSiteRegister = TheCallSiteDescription.RegisterSlots[CSV]; - const CallSite &TheCallSite = Q.first; - FunctionCallSlot FCS{ TheCallSite, Offset }; - - CallSiteRegister.Argument = FCRA[FCS]; - CallSiteRegister.Argument.notAvailable(); - CallSiteRegister.ReturnValue = FCRV[FCS]; - CallSiteRegister.ReturnValue.notAvailable(); - - if (FunctionEntry != nullptr) { - using FRegisterArgument = FunctionRegisterArgument; - using FReturnValue = FunctionReturnValue; - auto &Slot = P.second.RegisterSlots[CSV]; - Slot.Argument = FRegisterArgument(FRegisterArgument::Maybe); - Slot.ReturnValue = FReturnValue(FReturnValue::Maybe); - } - } - - continue; - } - - { - // - // Merge arguments - // - - // Register status at the function - const FunctionRegisterArgument &FunctionStatus = FRA[TheFunctionSlot]; - auto Status = FunctionStatus.value(); - revng_assert(Status == FunctionRegisterArgument::Maybe - or Status == FunctionRegisterArgument::NoOrDead - or Status == FunctionRegisterArgument::Contradiction - or Status == FunctionRegisterArgument::Yes - or Status == FunctionRegisterArgument::No); - - // Propagate information from the function to callers (and record if for - // at least a call site we have Yes information before the merge) - bool AtLeastAYes = false; - - for (auto &Q : FCS.CallSites) { - const CallSite &TheCallSite = Q.first; - revng_assert(Q.second != nullptr); - CallSiteDescription &TheCallSiteDescription = *Q.second; - FunctionCallSlot FCS{ TheCallSite, Offset }; - - // Register status at current call site - const FunctionCallRegisterArgument &CallerStatus = FCRA[FCS]; - auto Status = CallerStatus.value(); - revng_assert(Status == FunctionCallRegisterArgument::Maybe - or Status == FunctionCallRegisterArgument::Yes); - - // Register if there's at least a Yes - AtLeastAYes = (AtLeastAYes - or Status == FunctionCallRegisterArgument::Yes); - - // Update the status at the call site, starting from the status of the - // callee - FunctionCallRegisterArgument Result; - using FCRegisterArgument = FunctionCallRegisterArgument; - switch (FunctionStatus.value()) { - case FunctionRegisterArgument::Maybe: - Result = FCRegisterArgument(FCRegisterArgument::Maybe); - break; - case FunctionRegisterArgument::NoOrDead: - Result = FCRegisterArgument(FCRegisterArgument::NoOrDead); - break; - case FunctionRegisterArgument::Contradiction: - Result = FCRegisterArgument(FCRegisterArgument::Contradiction); - break; - case FunctionRegisterArgument::Yes: - Result = FCRegisterArgument(FCRegisterArgument::Yes); - break; - case FunctionRegisterArgument::No: - Result = FCRegisterArgument(FCRegisterArgument::No); - break; - default: - revng_abort(); - } - - // If the callee doesn't say No and the caller says yes - if (not(FunctionStatus.value() == FunctionRegisterArgument::No) - and CallerStatus.value() == FCRegisterArgument::Yes) { - // Promote caller using the Yes information - switch (FunctionStatus.value()) { - case FunctionRegisterArgument::NoOrDead: - Result = FCRegisterArgument(FCRegisterArgument::Dead); - break; - case FunctionRegisterArgument::Maybe: - Result = FCRegisterArgument(FCRegisterArgument::Yes); - break; - case FunctionRegisterArgument::Contradiction: - case FunctionRegisterArgument::Yes: - // Do nothing - break; - default: - revng_abort(); - } - } - - // In all other cases, no changes - - // Register the result - TheCallSiteDescription.RegisterSlots[CSV].Argument = Result; - } - - // Propagate the information from callers to function - FunctionRegisterArgument Result = FunctionStatus; - - if (AtLeastAYes) { - switch (FunctionStatus.value()) { - case FunctionRegisterArgument::Maybe: - Result = FunctionRegisterArgument(FunctionRegisterArgument::Yes); - break; - case FunctionRegisterArgument::NoOrDead: - Result = FunctionRegisterArgument(FunctionRegisterArgument::Dead); - break; - case FunctionRegisterArgument::Contradiction: - case FunctionRegisterArgument::Yes: - case FunctionRegisterArgument::No: - // Do nothing - break; - default: - revng_abort(); - } - } - - // Register the result for the argument of the function - P.second.RegisterSlots[CSV].Argument = Result; - } - - { - // - // Merge return values - // - - // Register status at the function - const FunctionReturnValue &FunctionStatus = FRV[TheFunctionSlot]; - auto Status = FunctionStatus.value(); - revng_assert(Status == FunctionReturnValue::Maybe - or Status == FunctionReturnValue::No - or Status == FunctionReturnValue::YesOrDead); - - // Propagate information from the function to callers (and record if at - // least on call sites says Yes or Dead) - bool AtLeastAYesOrDead = false; - for (auto &Q : FCS.CallSites) { - const CallSite &TheCallSite = Q.first; - auto &TheCallSiteDescription = *Q.second; - FunctionCallSlot FCS{ TheCallSite, Offset }; - - // Register status at current call site - const FunctionCallReturnValue &CallerStatus = FCRV[FCS]; - auto Status = CallerStatus.value(); - revng_assert(Status == FunctionCallReturnValue::Maybe - or Status == FunctionCallReturnValue::NoOrDead - or Status == FunctionCallReturnValue::Yes - or Status == FunctionCallReturnValue::Contradiction); - - FunctionCallReturnValue Result = CallerStatus; - - switch (FunctionStatus.value()) { - case FunctionReturnValue::No: - // No from the function is propagated as is - Result = FunctionCallReturnValue::no(); - break; - case FunctionReturnValue::YesOrDead: - // Propagate the strong yes information - switch (CallerStatus.value()) { - case FunctionCallReturnValue::Maybe: - Result = FunctionCallReturnValue(FunctionCallReturnValue::Yes); - break; - case FunctionCallReturnValue::NoOrDead: - Result = FunctionCallReturnValue(FunctionCallReturnValue::Dead); - break; - case FunctionCallReturnValue::Yes: - case FunctionCallReturnValue::Contradiction: - // Do nothing - break; - default: - revng_abort(); - } - break; - case FunctionReturnValue::Maybe: - break; - default: - revng_abort(); - } - - // In all other cases, no changes - - // Record if at least one result is Yes or Dead - { - auto Status = Result.value(); - AtLeastAYesOrDead = (AtLeastAYesOrDead - or Status == FunctionCallReturnValue::Yes - or Status == FunctionCallReturnValue::Dead); - } - - // Register the result for this call site - TheCallSiteDescription.RegisterSlots[CSV].ReturnValue = Result; - } - - // Cross-contamination of callers - if (AtLeastAYesOrDead) { - // If at least a call site states that this slot is a return value, - // all the other call sites can benefit from this information - - for (auto &Q : FCS.CallSites) { - using FCReturnValue = FunctionCallReturnValue; - auto &TheCallSiteDescription = *Q.second; - auto &Value = TheCallSiteDescription.RegisterSlots[CSV].ReturnValue; - switch (Value.value()) { - case FCReturnValue::NoOrDead: - Value = FCReturnValue(FCReturnValue::Dead); - break; - case FCReturnValue::Maybe: - Value = FCReturnValue(FCReturnValue::YesOrDead); - break; - case FCReturnValue::Yes: - case FCReturnValue::Dead: - case FCReturnValue::Contradiction: - // Do nothing - break; - case FCReturnValue::No: - default: - revng_abort(); - } - } - } - - // Update the result associated to the function - FunctionReturnValue Result = FunctionStatus; - using FCReturnValue = FunctionCallReturnValue; - - if (FCS.CallSites.size() > 0) { - // At this point the information associated to the call sites is - // either all "No", one of "Yes", "Dead" and "YesOrDead" or one of - // "NoOrDead" and "Maybe" - bool AllNo = true; - bool AllYesOrDead = true; - bool AllNoOrDead = true; - - // Initialize the result to propagate to the callee with the first - // call site - auto BeginIt = FCS.CallSites.begin(); - auto Accumulate = BeginIt->second->RegisterSlots[CSV].ReturnValue; - - for (auto &Q : FCS.CallSites) { - auto &TheCallSiteDescription = *Q.second; - auto &Value = TheCallSiteDescription.RegisterSlots[CSV].ReturnValue; - - AllNo = AllNo and Value.value() == FCReturnValue::No; - - auto Status = Value.value(); - AllYesOrDead = (AllYesOrDead - and (Status == FCReturnValue::Yes - or Status == FCReturnValue::Dead - or Status == FCReturnValue::YesOrDead)); - - AllNoOrDead = (AllNoOrDead - and (Status == FCReturnValue::NoOrDead - or Status == FCReturnValue::Maybe)); - - // If the value has changed, move towards the most generic - if (Status != Accumulate.value()) { - using FCReturnValue = FCReturnValue; - switch (Status) { - case FCReturnValue::Yes: - case FCReturnValue::Dead: - case FCReturnValue::YesOrDead: - Accumulate = FCReturnValue(FCReturnValue::YesOrDead); - break; - case FCReturnValue::NoOrDead: - case FCReturnValue::Maybe: - Accumulate = FCReturnValue(FCReturnValue::Maybe); - break; - case FCReturnValue::No: - case FCReturnValue::Contradiction: - revng_abort(); - } - } - } - - // AllNo XOR AllYesOrDead XOR AllNoOrDead - revng_assert((AllNo and not(AllYesOrDead or AllNoOrDead)) - or (AllYesOrDead and not(AllNo or AllNoOrDead)) - or (AllNoOrDead and not(AllYesOrDead or AllNo))); - - // Propagate the information from callers to function - - // If AllNo, nothing to do - using FReturnValue = FunctionReturnValue; - bool IsNo = FunctionStatus.value() == FReturnValue::No; - revng_assert(AllNo ? IsNo : true); - - // If the function status was maybe, we might have something to - // promote in the call - if (FunctionStatus.value() == FReturnValue::Maybe) { - switch (Accumulate.value()) { - case FCReturnValue::Yes: - case FCReturnValue::Dead: - case FCReturnValue::YesOrDead: - Result = FReturnValue(FReturnValue::YesOrDead); - break; - case FCReturnValue::NoOrDead: - Result = FReturnValue(FReturnValue::NoOrDead); - break; - case FCReturnValue::Maybe: - Result = FReturnValue(FReturnValue::Maybe); - break; - default: - revng_abort(); - } - } - } - - // Register the result with the function - P.second.RegisterSlots[CSV].ReturnValue = Result; - } - } - } - - return Result; -} - -} // namespace StackAnalysis diff --git a/lib/StackAnalysis/InterproceduralAnalysis.h b/lib/StackAnalysis/InterproceduralAnalysis.h deleted file mode 100644 index 442380955..000000000 --- a/lib/StackAnalysis/InterproceduralAnalysis.h +++ /dev/null @@ -1,230 +0,0 @@ -#pragma once - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include -#include -#include - -#include "llvm/ADT/Optional.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/IR/BasicBlock.h" -#include "llvm/IR/GlobalVariable.h" -#include "llvm/IR/User.h" -#include "llvm/Support/Casting.h" - -#include "revng/Support/Debug.h" -#include "revng/Support/IRHelpers.h" - -#include "Intraprocedural.h" - -/// \brief Logger for messages concerning the interprocedural analysis -extern Logger<> SaInterpLog; - -namespace StackAnalysis { - -/// \brief Class to collect the (partial) results of all the analyses -/// -/// Tracks register arguments and return values of functions and function calls, -/// the size of the stack at each call site, the type of each basic block -/// (branch) and the type of each function. -/// -/// The information stored here is "raw", as opposed to that stored in -/// `FunctionsSummary` which, e.g., merges the information available on a -/// function with the information coming from all of the call sites targeting -/// it. -class ResultsPool { - friend struct ClobberedRegistersAnalysis; - -public: - using BasicBlock = llvm::BasicBlock; - - template - using map = std::map; - - using BasicBlockTypeMap = map; - using StackSizeMap = map>; - -private: - using FunctionSlot = std::pair; - using FunctionCallSlot = std::pair; - -private: - // TODO: maps with the same keys could be merged - - // Data about functions - map FunctionRegisterArguments; - map FunctionReturnValues; - - // Data about function calls - using FCS = FunctionCallSlot; - map FunctionCallRegisterArguments; - map FunctionCallReturnValues; - - /// \brief Height of the stack at each call site - map> CallSites; - - /// \brief Classification of each branch - map BranchesType; - - /// \brief Classification of each function - map FunctionTypes; - - map> LocallyWrittenRegisters; - map> FakeReturns; - map> ExplicitlyCalleeSavedRegisters; - map> FunctionCalls; - -public: - /// \brief Register a function for which a summary is not available - void registerFunction(llvm::BasicBlock *Function, FunctionType::Values Type) { - FunctionTypes[Function] = Type; - } - - void registerFunction(llvm::BasicBlock *Entry, - FunctionType::Values Type, - const IntraproceduralFunctionSummary *Summary) { - registerFunction(Entry, Type); - if (Summary != nullptr) { - mergeCallSites(Entry, Summary->FrameSizeAtCallSite); - mergeBranches(Entry, Summary->BranchesType); - if (Type == FunctionType::Regular or Type == FunctionType::NoReturn) - mergeFunction(Entry, *Summary); - } - } - - /// \brief Merge data about \p Function in \p Summary into the results pool - /// - /// \param Function the entry basic block of the function we're currently - /// considering - /// \param Summary the final summary of the function from which the data about - /// registers and return values will be fetched - void mergeFunction(llvm::BasicBlock *Function, - const IntraproceduralFunctionSummary &Summary); - - /// \brief Merge data about the classification of a set of branches in \p - /// Function - void - mergeBranches(llvm::BasicBlock *Function, const BasicBlockTypeMap &Branches); - - /// \brief Merge information about the height of the stack at the call sites - /// of \p Function - void mergeCallSites(llvm::BasicBlock *Function, const StackSizeMap &ToImport); - - /// \brief Finalized the data stored in this object and produce a - /// FunctionsSummary - FunctionsSummary finalize(llvm::Module *M, Cache *TheCache); - - void dump(const llvm::Module *M) const debug_function { dump(M, dbg); } - - template - void dump(const llvm::Module *M, T &Output) const { - Output << "CallSites:\n"; - for (auto &P : CallSites) { - const CallSite &TheCallSite = P.first; - llvm::Optional StackHeight = P.second; - - TheCallSite.dump(Output); - Output << ": "; - if (StackHeight) - Output << *StackHeight; - else - Output << "unknown"; - - Output << "\n"; - } - - Output << "FunctionRegisterArguments:\n"; - for (auto &P : FunctionRegisterArguments) { - Output << getName(P.first.first) << " "; - ASSlot::create(ASID::cpuID(), P.first.second).dump(M, Output); - Output << ": "; - P.second.dump(Output); - Output << "\n"; - } - Output << "\n"; - - Output << "FunctionReturnValues:\n"; - for (auto &P : FunctionReturnValues) { - Output << getName(P.first.first) << " "; - ASSlot::create(ASID::cpuID(), P.first.second).dump(M, Output); - Output << ": "; - P.second.dump(Output); - Output << "\n"; - } - Output << "\n"; - - Output << "FunctionCallRegisterArguments:\n"; - for (auto &P : FunctionCallRegisterArguments) { - P.first.first.dump(Output); - Output << " "; - ASSlot::create(ASID::cpuID(), P.first.second).dump(M, Output); - Output << ": "; - P.second.dump(Output); - Output << "\n"; - } - Output << "\n"; - - Output << "FunctionCallReturnValues:\n"; - for (auto &P : FunctionCallReturnValues) { - P.first.first.dump(Output); - Output << " "; - ASSlot::create(ASID::cpuID(), P.first.second).dump(M, Output); - Output << ": "; - P.second.dump(Output); - Output << "\n"; - } - Output << "\n"; - } - - /// \brief Build a set of all the `BasicBlock`s that have been visited so far - std::set visitedBlocks() const { - std::set Result; - for (auto &P : BranchesType) - Result.insert(P.first.branch()->getParent()); - return Result; - } -}; - -/// \brief Interprocedural part of the stack analysis -class InterproceduralAnalysis { -private: - using Analysis = Intraprocedural::Analysis; - -private: - Cache &TheCache; - GeneratedCodeBasicInfo &GCBI; - std::vector InProgress; - std::set InProgressFunctions; ///< For recursion detection - -public: - InterproceduralAnalysis(Cache &TheCache, GeneratedCodeBasicInfo &GCBI) : - TheCache(TheCache), GCBI(GCBI) {} - - void run(llvm::BasicBlock *Entry, ResultsPool &Results); - -private: - void push(llvm::BasicBlock *Entry); - - void popUntil(const Analysis *WI) { - while (&InProgress.back() != WI) - pop(); - } - - const Analysis *getRecursionRoot(llvm::BasicBlock *Entry) const { - for (const Analysis &WI : InProgress) - if (WI.entry() == Entry) - return &WI; - - return nullptr; - } - - void pop() { - InProgressFunctions.erase(InProgress.back().entry()); - InProgress.pop_back(); - } -}; - -} // namespace StackAnalysis diff --git a/lib/StackAnalysis/Intraprocedural.cpp b/lib/StackAnalysis/Intraprocedural.cpp deleted file mode 100644 index 4be55cb99..000000000 --- a/lib/StackAnalysis/Intraprocedural.cpp +++ /dev/null @@ -1,1192 +0,0 @@ -/// \file Intraprocedural.cpp -/// \brief Implementation of the intraprocedural portion of the stack analysis - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include - -#include "Cache.h" -#include "InterproceduralAnalysis.h" -#include "Intraprocedural.h" - -using llvm::AllocaInst; -using llvm::ArrayRef; -using llvm::BasicBlock; -using llvm::BlockAddress; -using llvm::CallInst; -using llvm::cast; -using llvm::Constant; -using llvm::ConstantInt; -using llvm::DataLayout; -using llvm::dyn_cast; -using llvm::GlobalVariable; -using llvm::Instruction; -using llvm::isa; -using llvm::LoadInst; -using llvm::Module; -using llvm::Optional; -using llvm::SmallVector; -using llvm::StoreInst; -using llvm::Type; -using llvm::UndefValue; -using llvm::UnreachableInst; -using llvm::User; - -using AI = StackAnalysis::Intraprocedural::Interrupt; -using IFS = StackAnalysis::IntraproceduralFunctionSummary; - -const IFS EmptyCallSummary = IFS::bottom(); - -// Loggers -static Logger<> SaFake("sa-fake"); -static Logger<> SaTerminator("sa-terminator"); -static Logger<> SaBBLog("sa-bb"); - -// Statistics -RunningStatistics ABIRegistersCountStats("ABIRegistersCount"); -static RunningStatistics CacheHitRate("CacheHitRate"); - -/// \brief Per-function cache hit rate -static std::map FunctionCacheHitRate; - -/// \brief Round \p Value to \p Digits -template -static std::string round(F Value, int Digits) { - std::stringstream Stream; - Stream << std::setprecision(Digits) << Value; - return Stream.str(); -} - -namespace StackAnalysis { - -namespace Intraprocedural { - -void Analysis::initialize() { - CacheMustHit = false; - - revng_log(SaLog, "Creating Analysis for " << getName(Entry)); - - Instruction *T = Entry->getTerminator(); - revng_assert(T != nullptr); - - // Obtain the link register used to call this function - GlobalVariable *LinkRegister = TheCache->getLinkRegister(Entry); - - // Get the register indices for for the stack pointer, the program counter - // and the link register - int32_t LinkRegisterIndex = 0; - if (LinkRegister != nullptr) - LinkRegisterIndex = TheCache->getCPUIndex(LinkRegister); - PCIndex = TheCache->getCPUIndex(GCBI->pcReg()); - SPIndex = TheCache->getCPUIndex(GCBI->spReg()); - - // Set the stack pointer to SP0+0 - ASSlot StackPointer = ASSlot::create(ASID::cpuID(), SPIndex); - ASSlot StackSlot0 = ASSlot::create(ASID::stackID(), 0); - InitialState = Element::initial(); - InitialState.store(Value::fromSlot(StackPointer), - Value::fromSlot(StackSlot0)); - - // Record the slot where ther return address is stored - ReturnAddressSlot = ((LinkRegister == nullptr) ? - StackSlot0 : - ASSlot::create(ASID::cpuID(), LinkRegisterIndex)); - - if (SaLog.isEnabled()) { - SaLog << "The return address is in "; - if (LinkRegister != nullptr) - SaLog << LinkRegister->getName().str(); - else - SaLog << "the top of the stack"; - SaLog << DoLog; - } - - TheABIIR.reset(); - IncoherentFunctions.clear(); - SuccessorsMap.clear(); - Base::initialize(); -} - -/// \brief Class to keep track of the Value associated to each instruction in a -/// basic block -class BasicBlockState { -public: - using ContentMap = std::map; - -private: - BasicBlock *BB; - const Module *M; - ContentMap InstructionContent; ///< Map for the instructions in this BB - ContentMap &VariableContent; ///< Reference to map for allocas - const DataLayout &DL; - const Cache *TheCache; - -public: - BasicBlockState(BasicBlock *BB, - ContentMap &VariableContent, - const DataLayout &DL, - const Cache *TheCache) : - BB(BB), - M(getModule(BB)), - VariableContent(VariableContent), - DL(DL), - TheCache(TheCache) {} - - /// \brief Gets the Value associated to \p V - /// - /// This function handles a couple of type of llvm::Values: - /// - /// * AllocaInst/GlobalVariables: represent a part of the CPU state, the - /// result will be an ASSlot relative to the CPU address space. - /// * Constant: represent an absolute address, the result will be an ASSlot - /// relative to the GLB address space with an offset equal to the actual - /// value of the constant. - /// * Instruction: represents the result of a (previously analyzed) - /// Instruction. It can be any Value. - Value get(llvm::Value *V) const { - V = skipCasts(V); - - if (auto *CSV = dyn_cast(V)) { - - return Value::fromSlot(ASID::cpuID(), TheCache->getCPUIndex(CSV)); - - } else if (auto *CSV = dyn_cast(V)) { - - if (TheCache->isCPU(CSV)) - return Value::fromSlot(ASID::cpuID(), TheCache->getCPUIndex(CSV)); - else - return Value(); - - } else if (isa(V)) { - - return Value(); - - } else if (auto *C = dyn_cast(V)) { - - Type *T = C->getType(); - if (T->isPointerTy() or T->getIntegerBitWidth() <= 64) { - int32_t Offset = getZExtValue(C, DL); - return Value::fromSlot(ASID::globalID(), Offset); - } else { - return Value(); - } - } - - Instruction *I = cast(V); - - // I shoudl be in InstructionContent or VariableContent - auto InstructionContentIt = InstructionContent.find(I); - auto VariableContentIt = VariableContent.find(I); - - if (InstructionContentIt != InstructionContent.end()) - return InstructionContentIt->second; - else if (VariableContentIt != VariableContent.end()) - return VariableContentIt->second; - else - revng_abort(); - } - - /// \brief Register the value of instruction \p I - void set(Instruction *I, Value V) { - revng_assert(I != nullptr); - revng_assert(I->getParent() == BB, - "Instruction from an unexpected basic block"); - revng_assert(InstructionContent.count(I) == 0, - "Instruction met more than once in a basic block"); - - if (SaVerboseLog.isEnabled()) { - SaVerboseLog << "Set " << getName(I) << " to "; - V.dump(M, SaVerboseLog); - SaVerboseLog << DoLog; - } - - InstructionContent[I] = V; - } - - // TODO: this probably needs to be able to handle casts only - /// \brief Handle automatically an otherwise un-handleable instruction - /// - /// This is a fallback handling of instruction not otherwise manually - /// handled. The resulting Value will be a combination of the Values of all - /// its operands. - void handleGenericInstruction(Instruction *I) { - revng_assert(I->getParent() == BB, - "Instruction from an unexpected basic block"); - - switch (I->getOpcode()) { - case Instruction::BitCast: - case Instruction::IntToPtr: - case Instruction::PtrToInt: - case Instruction::ZExt: - case Instruction::SExt: - revng_assert(I->getNumOperands() == 1); - set(I, get(I->getOperand(0))); - break; - - default: - set(I, Value::empty()); - break; - } - } - - /// \brief Compute the set of BasicBlocks affected by changes in the current - /// one - std::set computeAffected() { - std::set Result; - for (auto &P : InstructionContent) { - Instruction *I = P.first; - Value &NewValue = P.second; - - revng_assert(I->getParent() == BB); - - if (I->isUsedOutsideOfBlock(BB)) { - bool Changed = false; - - // Has this instruction ever been registered? - auto It = VariableContent.find(I); - if (It == VariableContent.end()) { - VariableContent[I] = NewValue; - Changed = true; - } else { - // If not, are we saying something new? - Value &OldValue = It->second; - if (not NewValue.lowerThanOrEqual(OldValue)) { - OldValue = NewValue; - Changed = true; - } - } - - if (Changed) { - for (User *U : I->users()) { - if (auto *UserI = dyn_cast(U)) { - BasicBlock *UserBB = UserI->getParent(); - if (UserBB != BB) - Result.insert(UserBB); - } - } - } - } - } - - return Result; - } -}; - -static llvm::Value *getModifyAndReassign(Instruction *I) { - auto *Load = dyn_cast(I->getOperand(0)); - if (Load == nullptr) - return nullptr; - - for (User *U : I->users()) { - auto *Store = dyn_cast(U); - if (Store != nullptr and Store->getValueOperand() == I - and Load->getPointerOperand() == Store->getPointerOperand()) { - return Load->getPointerOperand(); - } - } - - return nullptr; -} - -Interrupt Analysis::transfer(BasicBlock *BB) { - auto SP0 = ASID::stackID(); - - BlockType::Values Type = GCBI->getType(BB); - revng_assert(Type != BlockType::AnyPCBlock - and Type != BlockType::UnexpectedPCBlock); - - // Create a copy of the initial state associated to this basic block - auto It = State.find(BB); - revng_assert(It != State.end()); - Element Result = It->second.copy(); - - revng_log(SaBBLog, "Analyzing " << getName(BB)); - LoggerIndent<> Y(SaBBLog); - - if (SaLog.isEnabled()) { - SaLog << "Analyzing basic block " << getName(BB) << DoLog; - Result.dump(M, SaLog); - SaLog << DoLog; - } - - // Reset the basic ABI IR basic block - ABIIRBasicBlock &ABIBB = TheABIIR.get(BB); - ABIBB.clear(); - - // TODO: prune all the info about dead instructions - - // Initialize an object to keep track of the values associated to each - // instruction in the current basic block - BasicBlockState BBState(BB, VariableContent, M->getDataLayout(), TheCache); - - for (Instruction &I : *BB) { - - revng_log(SaVerboseLog, "NewInstruction: " << getName(&I)); - - switch (I.getOpcode()) { - case Instruction::Load: { - auto *Load = cast(&I); - - // Get the value associated to the pointer operand and load from it from - // Result - const Value &AddressValue = BBState.get(Load->getPointerOperand()); - BBState.set(&I, Result.load(AddressValue)); - - // If it's not an identity load and we're loading from a register or the - // stack, register the load in the ABI IR - if (not TheCache->isIdentityLoad(Load)) { - if (const ASSlot *Target = AddressValue.directContent()) { - if (isCSV(*Target) or Target->addressSpace() == SP0) - ABIBB.append(ABIIRInstruction::createLoad(*Target)); - } - } - - } break; - - case Instruction::Store: { - auto *Store = cast(&I); - - // Completely ignore identity stores - if (TheCache->isIdentityStore(Store)) - break; - - // Update slot Address in Result with StoredValue - Value Address = BBState.get(Store->getPointerOperand()); - Value StoredValue = BBState.get(Store->getValueOperand()); - Result.store(Address, StoredValue); - - // If we're loading from a register or the stack register the store in - // the ABI IR - if (const ASSlot *Target = Address.directContent()) - if (isCSV(*Target) or Target->addressSpace() == SP0) - ABIBB.append(ABIIRInstruction::createStore(*Target)); - - } break; - - case Instruction::And: { - // If we're masking an address with a mask that is at most as strict as - // the one for instruction alignment, ignore the operation. This allows - // us to correctly track value whose lower bits are suppressed before - // being written to the PC. - // Note that this works if the address is pointing to code, but not - // necessarily if it's pointing to data. - Value FirstOperand = BBState.get(I.getOperand(0)); - if (auto *SecondOperand = dyn_cast(I.getOperand(1))) { - uint64_t Mask = getSignedLimitedValue(SecondOperand); - uint64_t Flip = ~Mask + 1; - bool IsContiguousMask = Flip and not(Flip & (Flip - 1)); - - if (IsContiguousMask) { - bool Forward = false; - - // Forward any contiguous mask applied to the stack pointer, it's - // likely stack alignment - llvm::Value *Pointer = getModifyAndReassign(&I); - if (Pointer != nullptr and GCBI->isSPReg(Pointer)) { - Forward = true; - } else { - uint64_t SignificantPCBits; - if (GCBI->pcRegSize() == 4) { - SignificantPCBits = std::numeric_limits::max(); - } else { - revng_assert(GCBI->pcRegSize() == 8); - SignificantPCBits = std::numeric_limits::max(); - } - uint64_t AlignmentMask = GCBI->instructionAlignment() - 1; - SignificantPCBits = SignificantPCBits & ~AlignmentMask; - - Forward = (SignificantPCBits & Mask) == SignificantPCBits; - } - - if (Forward) { - BBState.set(&I, FirstOperand); - break; - } - } - } - - // In all other cases, treat it as a regular instruction - BBState.handleGenericInstruction(&I); - } break; - - case Instruction::Add: - case Instruction::Sub: { - int Sign = (I.getOpcode() == Instruction::Add) ? +1 : -1; - - // If the second operand is constant we can handle it - Value FirstOperand = BBState.get(I.getOperand(0)); - if (auto *Addend = dyn_cast(I.getOperand(1))) { - if (FirstOperand.add(Sign * getLimitedValue(Addend))) { - BBState.set(&I, FirstOperand); - break; - } - } - - // In all other cases, treat it as a regular instruction - BBState.handleGenericInstruction(&I); - } break; - - case Instruction::Call: { - auto *Call = cast(&I); - - // If the call returns something, introduce a dummy value in BBState - if (not Call->getFunctionType()->getReturnType()->isVoidTy()) - BBState.set(&I, Value::empty()); - - FunctionCall Indirect(nullptr, &I); - - const llvm::Function *Callee = getCallee(&I); - revng_assert(Callee != nullptr); - // We should have function calls to helpers, markers, abort or - // intrinsics. Assert in other cases. - revng_assert(isCallToHelper(&I) || isMarker(&I) - || Callee->getName() == "abort" || Callee->isIntrinsic()); - - if (isCallToHelper(&I)) { - - // Compute the stack size for the call to the helper - Optional CallerStackSize = stackSize(Result); - - // Register the call site (as an indirect call) along with the current - // stack size - registerStackSizeAtCallSite(Indirect, CallerStackSize); - - // Create in the ABIIR a load for each read register and a store for - // each written register - auto UsedCSVs = GeneratedCodeBasicInfo::getCSVUsedByHelperCall(Call); - - for (GlobalVariable *CSV : UsedCSVs.Read) - if (TheCache->isCSV(CSV)) - ABIBB.append(ABIIRInstruction::createLoad(slotFromCSV(CSV))); - - for (GlobalVariable *CSV : UsedCSVs.Written) - if (TheCache->isCSV(CSV)) - ABIBB.append(ABIIRInstruction::createStore(slotFromCSV(CSV))); - } - - } break; - - case Instruction::Br: - case Instruction::Switch: { - - // We're at the end of the basic block, handleTerminator will provide us - // an Interrupt to forward back - Interrupt BBResult = handleTerminator(&I, Result, ABIBB); - - // Register all the successors in the ABI IR too - if (BBResult.hasSuccessors()) - for (BasicBlock *BB : BBResult) - ABIBB.addSuccessor(&TheABIIR.get(BB)); - - // Record the type of this branch - BranchesType[BB] = BBResult.type(); - - // Re-enqueue for analysis all the basic block affected by changes in - // the current one - std::set ToReanalyze = BBState.computeAffected(); - for (BasicBlock *BB : ToReanalyze) - if (GCBI->getType(BB) != BlockType::IndirectBranchDispatcherHelperBlock) - registerToVisit(BB); - - if (SaLog.isEnabled()) { - SaLog << "Basic block terminated: " << getName(BB) << "\n"; - BBResult.dump(M, SaLog); - SaLog << DoLog; - } - - return BBResult; - } - - case Instruction::Unreachable: - return AI::create(std::move(Result), BranchType::Unreachable); - - default: - BBState.handleGenericInstruction(&I); - break; - } - - revng_assert(Result.verify()); - } - - revng_abort(); -} - -static SmallVector -directSuccessors(GeneratedCodeBasicInfo *GCBI, Instruction *T) { - revng_assert(T->isTerminator()); - SmallVector Successors; - for (BasicBlock *Successor : llvm::successors(T)) { - BlockType::Values SuccessorType = GCBI->getType(Successor); - if (SuccessorType != BlockType::UnexpectedPCBlock - and SuccessorType != BlockType::AnyPCBlock) - Successors.push_back(Successor); - } - return Successors; -} - -Interrupt Analysis::handleTerminator(Instruction *T, - Element &Result, - ABIIRBasicBlock &ABIBB) { - namespace BT = BranchType; - BasicBlock *BB = T->getParent(); - FakeReturns.erase(BB); - - revng_assert(T->isTerminator()); - revng_assert(not isa(T)); - - LogOnReturn<> X(SaTerminator); - SaTerminator << T; - - Value StackPointer = Value::fromSlot(ASID::cpuID(), SPIndex); - - bool HasUnknownStackSize = not Result.load(StackPointer).hasDirectContent(); - if (HasUnknownStackSize) - SaTerminator << " UnknownStackSize"; - - // 0. Check if it's a direct killer basic block - // TODO: we should move the metadata enums and functions to get their names to - // GCBI - // TODO: this is likely wrong - if (GCBI->isKiller(T) - and GCBI->getKillReason(T) != KillReason::LeadsToKiller) { - SaTerminator << " Killer"; - return AI::create(std::move(Result), BT::Killer); - } - - // 1. Check if we're dealing with instruction-local control flow (e.g., the if - // generated due to a conditional move) - // 2. Check if it's an indirect branch, which means that "anypc" is among - // its successors - bool IsInstructionLocal = false; - bool IsIndirect = false; - bool IsUnresolvedIndirect = false; - bool JustUnexpected = true; - - for (BasicBlock *Successor : llvm::successors(T)) { - BlockType::Values SuccessorType = GCBI->getType(Successor->getTerminator()); - - // If at least one successor is not a jump target, the branch is instruction - // local - namespace BT = BlockType; - constexpr auto IBDHB = BT::IndirectBranchDispatcherHelperBlock; - IsInstructionLocal = (IsInstructionLocal - or SuccessorType == BT::TranslatedBlock - or SuccessorType == IBDHB); - - revng_assert(SuccessorType != BT::RootDispatcherBlock); - - IsIndirect = (IsIndirect or SuccessorType == BT::AnyPCBlock - or SuccessorType == BT::UnexpectedPCBlock - or SuccessorType == IBDHB); - - IsUnresolvedIndirect = (IsUnresolvedIndirect - or SuccessorType == BT::AnyPCBlock); - - JustUnexpected = JustUnexpected and SuccessorType == BT::UnexpectedPCBlock; - } - - if (IsIndirect) - SaTerminator << " IsIndirect"; - - if (IsUnresolvedIndirect) - SaTerminator << " IsUnresolvedIndirect"; - - if (IsInstructionLocal) { - SaTerminator << " IsInstructionLocal"; - - return AI::createWithSuccessors(std::move(Result), - BT::InstructionLocalCFG, - directSuccessors(GCBI, T)); - } - - // 3. Check if this a function call (although the callee might not be a proper - // function) - bool IsFunctionCall = false; - BasicBlock *Callee = nullptr; - BasicBlock *ReturnFromCall = nullptr; - MetaAddress ReturnAddress = MetaAddress::invalid(); - - if (CallInst *Call = getFunctionCall(T->getParent())) { - IsFunctionCall = true; - auto *Arg0 = Call->getArgOperand(0); - auto *Arg1 = Call->getArgOperand(1); - auto *Arg2 = Call->getArgOperand(2); - - if (auto *CalleeBlockAddress = dyn_cast(Arg0)) - Callee = CalleeBlockAddress->getBasicBlock(); - - auto *ReturnBlockAddress = cast(Arg1); - ReturnFromCall = ReturnBlockAddress->getBasicBlock(); - - ReturnAddress = MetaAddress::fromConstant(Arg2); - - SaTerminator << " IsFunctionCall (callee " << Callee << ", return " - << ReturnFromCall << ")"; - } - - // 4. Check if the stack pointer is in position valid for returning - - // Get the current value of the stack pointer - // TODO: we should evaluate the approximation introduced here appropriately - Value StackPointerValue = Result.load(StackPointer); - - const ASSlot *StackPointerSlot = StackPointerValue.directContent(); - - auto SP0 = ASID::stackID(); - bool IsReadyToReturn = (StackPointerSlot != nullptr - and StackPointerSlot->addressSpace() == SP0 - and StackPointerSlot->offset() >= 0); - - if (IsReadyToReturn) - SaTerminator << " IsReadyToReturn"; - - // 5. Are we jumping to the return address? Are we jumping to the return - // address from a fake function? - bool IsReturn = false; - bool IsReturnFromFake = false; - uint64_t FakeFunctionReturnAddress = 0; - - if (IsIndirect) { - // Get the current value being stored in the program counter - Value ProgramCounter = Value::fromSlot(ASID::cpuID(), PCIndex); - Value ProgramCounterValue = Result.load(ProgramCounter); - - const ASSlot *PCContent = ProgramCounterValue.directContent(); - const ASSlot *PCTag = ProgramCounterValue.tag(); - - // It's a return if the PC has a value with a name matching the name of the - // initial value of the link register - IsReturn = (PCTag != nullptr) and (*PCTag == ReturnAddressSlot); - - if (SaTerminator.isEnabled()) { - - if (IsReturn) { - SaTerminator << " ReturnsToLinkRegister"; - } else { - SaTerminator << " ("; - - ReturnAddressSlot.dump(M, SaTerminator); - SaTerminator << " != "; - if (PCTag == nullptr) - SaTerminator << "nullptr"; - else - PCTag->dump(M, SaTerminator); - SaTerminator << ")"; - } - } - - if (PCContent != nullptr) { - // Check if it's a return from fake - if (!IsReturn) { - if (PCContent->addressSpace() == ASID::globalID()) { - uint64_t Offset = PCContent->offset(); - FakeFunctionReturnAddress = Offset; - IsReturnFromFake = FakeReturnAddresses.count(Offset) != 0; - } - } - } - } - - if (IsReturnFromFake) - SaTerminator << " IsReturnFromFake"; - - // 6. Using the collected information, classify the branch type - - // Are we returning to the return address? - if (IsReturn) { - // This looks like an actual return - insert_or_assign(ReturnCandidates, T->getParent(), Result.copy()); - return AI::create(std::move(Result), BT::Return); - } - - if (IsFunctionCall) - return handleCall(T, Callee, ReturnAddress, ReturnFromCall, Result, ABIBB); - - // Is it an indirect jump? - if (IsIndirect) { - - // Is it targeting an address that we registered as a return from fake - // function call? - if (IsReturnFromFake) { - // Continue from there - MetaAddress MA = GCBI->fromPC(FakeFunctionReturnAddress); - FakeReturns.insert({ BB, MA }); - BasicBlock *ReturnBB = GCBI->getBlockAt(MA); - return AI::createWithSuccessor(std::move(Result), - BT::FakeFunctionReturn, - ReturnBB); - } - - // Check if it's a real indirect jump, i.e. we're not 100% of the targets - if (IsUnresolvedIndirect or JustUnexpected) { - if (IsReadyToReturn) { - // If the stack is not in a valid position, we consider it an indirect - // tail call - return handleCall(T, - nullptr, - MetaAddress::invalid(), - nullptr, - Result, - ABIBB); - } else { - // We have an indirect jump with a stack not ready to return: it's a - // longjmp - return AI::create(std::move(Result), BT::LongJmp); - } - } - } - - SaTerminator << " FunctionLocalCFG"; - return AI::createWithSuccessors(std::move(Result), - BT::FunctionLocalCFG, - directSuccessors(GCBI, T)); -} - -std::pair Analysis::finalize() { - MetaAddress EntryPC = getPC(Entry->getTerminator()).first; - -#ifndef NDEBUG - // Compute the set of reachable basic blocks - llvm::ReversePostOrderTraversal RPOT(TheABIIR.entry()); - std::set Reachable; - for (ABIIRBasicBlock *Block : RPOT) - Reachable.insert(Block->basicBlock()); -#endif - - // - // Return SP election - // - - // Combine the value of the stack pointer of each candidate return to see if - // they agree. Meanwhile find the return that is closest (but after) the - // entry point and that has a valid stack size - Value BestSP; - uint64_t ClosestPC = EntryPC.address() - 1; - bool First = true; - Value Combined; - for (auto &P : ReturnCandidates) { - BasicBlock *BB = P.first; - const Element &Result = P.second; - MetaAddress PC = getPC(BB->getTerminator()).first; - uint64_t PCAddress = PC.address(); - -#ifndef NDEBUG - revng_assert(Reachable.count(BB) != 0); -#endif - - Value StackPointer = Value::fromSlot(ASID::cpuID(), SPIndex); - Value StackPointerValue = Result.load(StackPointer); - - if (First) { - Combined = StackPointerValue; - First = false; - } else { - Combined.combine(StackPointerValue); - } - - if (const ASSlot *Slot = StackPointerValue.directContent()) { - if (PC.addressGreaterThanOrEqual(EntryPC) and PCAddress < ClosestPC - and Slot->addressSpace() == ASID::stackID() and Slot->offset() >= 0) { - ClosestPC = PCAddress; - BestSP = StackPointerValue; - } - } - } - - // Do they all agree on a fixed stack pointer? - if (const ASSlot *Slot = Combined.directContent()) { - if (Slot->addressSpace() == ASID::stackID()) { - if (Slot->offset() >= 0) { - BestSP = Combined; - } else { - // Every return agrees the stack has grown: it's a fake function, let's - // inline it - return { FunctionType::Fake, Element::bottom() }; - } - } - } - - if (not BestSP.hasDirectContent()) - return { FunctionType::NoReturn, Element::bottom() }; - - // Combine all the values of the non-broken returns, mark as broken all the - // others - First = true; - Element GrandResult = Element::bottom(); - for (auto &P : ReturnCandidates) { - BasicBlock *BB = P.first; - Element &ReturnResult = P.second; - - Value StackPointer = Value::fromSlot(ASID::cpuID(), SPIndex); - Value StackPointerValue = ReturnResult.load(StackPointer); - - if (BestSP.hasDirectContent() and StackPointerValue == BestSP) { - // Mark as return basic block in the ABI IR - TheABIIR.get(BB).setReturn(); - - // OK, we're compatible, make ReturnResult part of the final result - if (First) { - GrandResult = std::move(ReturnResult); - First = false; - } else { - GrandResult.combine(std::move(ReturnResult)); - } - } else { - // Mark as broken - auto &Type = BranchesType[BB]; - if (Type == BranchType::Return) - Type = BranchType::BrokenReturn; - else if (Type == BranchType::IndirectTailCall) - Type = BranchType::LongJmp; - } - } - - return { FunctionType::Regular, std::move(GrandResult) }; -} - -Interrupt Analysis::handleCall(Instruction *Caller, - BasicBlock *Callee, - MetaAddress ReturnAddress, - BasicBlock *ReturnFromCall, - Element &Result, - ABIIRBasicBlock &ABIBB) { - namespace BT = BranchType; - - revng_assert(Callee == nullptr or getName(Callee) != "unexpectedpc"); - - const bool IsRecursive = InProgressFunctions.count(Callee) != 0; - const bool IsIndirect = (Callee == nullptr); - const bool IsIndirectTailCall = IsIndirect and (ReturnFromCall == nullptr); - bool IsKiller = false; - bool ABIOnly = false; - - FunctionCall TheFunctionCall = { Callee, Caller }; - int32_t PCRegSize = GCBI->pcRegSize(); - - Value StackPointer = Value::fromSlot(ASID::cpuID(), SPIndex); - Value OldStackPointer = Result.load(StackPointer); - Value PC = Value::fromSlot(ASID::cpuID(), PCIndex); - - // Handle special function types: - // - // 1. Calls to Fake functions will be inlined. - // 2. Calls to NoReturn functions will make the current basic block a Killer - // 3. Calls to IndirectTailCall functions are considered as indirect function - // calls - if (not IsIndirect) { - if (TheCache->isFakeFunction(Callee)) { - // Make sure the CacheMustHit bit is turned off - resetCacheMustHit(); - - SaTerminator << " IsFakeFunctionCall"; - // Assume normal control flow (i.e., inline) - FakeReturnAddresses.insert(ReturnAddress.asPC()); - return AI::createWithSuccessor(std::move(Result), - BT::FakeFunctionCall, - Callee); - } else if (TheCache->isNoReturnFunction(Callee)) { - SaTerminator << " IsNoReturnFunction"; - ABIOnly = true; - } - } - - // If we know the current stack frame size, copy the arguments - Optional CallerStackSize = stackSize(Result); - - const IFS *CallSummary = &EmptyCallSummary; - - revng_assert(not(IsRecursive && IsIndirect)); - - // Is it an direct function call? - if (not IsIndirect) { - // We have a direct call - revng_assert(Callee != nullptr); - - // It's a direct function call, lookup the pair in the - // cache - Optional CacheEntry; - CacheEntry = TheCache->get(Callee); - - if (not CacheMustHit) { - const char *ResultString = nullptr; - if (CacheEntry) { - CacheHitRate.push(1); - FunctionCacheHitRate[Callee].push(1); - ResultString = "hit"; - } else { - CacheHitRate.push(0); - FunctionCacheHitRate[Callee].push(0); - ResultString = "miss"; - } - - if (SaInterpLog.isEnabled()) { - SaInterpLog << "Cache " << ResultString << " for " << Callee << " at " - << Caller << " ("; - auto Mean = FunctionCacheHitRate[Callee].mean(); - SaInterpLog << "function hit rate: " << round(100 * Mean, 4) << "%"; - SaInterpLog << ", hit rate: " << round(100 * CacheHitRate.mean(), 4) - << "%) "; - SaInterpLog << DoLog; - } - } - - // Do we have a cache hit? - // If we don't we return control the interprocedural part, and we record - // that next time we *must* have a cache hit. If we don't there's the risk - // we're going to loop endlessly. - if (CacheEntry) { - resetCacheMustHit(); - - // We have a match in the cache - CallSummary = *CacheEntry; - } else { - // Ensure we don't get a cache miss twice in a row - revng_assert(not CacheMustHit); - - // Next time the cache will have to hit - CacheMustHit = true; - - // We don't have a match in the cache. Ask interprocedural analysis to - // analyze this function call with the current context - return AI::createUnhandledCall(Callee); - } - - } // not IsIndirect - - // If we got to this point, we now have a cached result of what the callee - // does. Let's apply it. - - if (SaLog.isEnabled()) { - SaLog << "The summary result for a call to " << getName(Callee) << " is\n"; - CallSummary->dump(M, SaLog); - SaLog << DoLog; - } - - if (not ABIOnly and not CallSummary->FinalState.isBottom()) { - // Use the summary from the cache - Result.apply(CallSummary->FinalState); - } - - if (IsRecursive or IsIndirect) { - ABIBB.append(ABIIRInstruction::createIndirectCall(TheFunctionCall)); - } else { - std::set StackArguments; - if (CallerStackSize and *CallerStackSize >= 0) - StackArguments = CallSummary->FinalState.stackArguments(*CallerStackSize); - ABIBB.append(ABIIRInstruction::createDirectCall(TheFunctionCall, - CallSummary->ABI.copy(), - StackArguments)); - } - - // Record frame size - registerStackSizeAtCallSite(TheFunctionCall, CallerStackSize); - - // Resume the analysis from where we left off - - // Restore the stack pointer - GlobalVariable *CalleeLinkRegister = TheCache->getLinkRegister(Callee); - if (CalleeLinkRegister == nullptr) { - // Increase the stack pointer of the size of the PC reg - OldStackPointer.add(PCRegSize); - } - Result.store(StackPointer, OldStackPointer); - - // Restore the PC - // TODO: handle return address from indirect tail calls - ASSlot ReturnAddressSlot = ASSlot::create(ASID::globalID(), - ReturnAddress.asPCOrZero()); - Result.store(PC, Value::fromSlot(ReturnAddressSlot)); - - revng_assert(not(IsIndirectTailCall and IsKiller)); - if (IsIndirectTailCall) { - // We consider indirect tail calls as returns - insert_or_assign(ReturnCandidates, Caller->getParent(), Result.copy()); - return AI::create(std::move(Result), BT::IndirectTailCall); - } else if (IsKiller) { - return AI::create(std::move(Result), BT::Killer); - } else { - revng_assert(ReturnFromCall != nullptr); - auto Reason = IsIndirect ? BT::IndirectCall : BT::HandledCall; - return AI::createWithSuccessor(std::move(Result), Reason, ReturnFromCall); - } -} - -ASSlot Analysis::slotFromCSV(llvm::User *U) const { - return ASSlot::create(ASID::cpuID(), TheCache->getCPUIndex(U)); -} - -IFS Analysis::createSummary() { - auto P = finalize(); - FunctionType::Values Type = P.first; - Element GrandResult = std::move(P.second); - - // Fake functions need no further analysis (NoReturn functions do) - if (Type == FunctionType::Fake) - return IFS::createFake(); - - // Finalize the ABI IR (e.g., fill-in reverse links) - TheABIIR.finalize(); - - FunctionABI ABI; - - if (SaABI.isEnabled()) { - revng_log(SaABI, "Starting analysis of " << Entry); - TheABIIR.dump(SaABI, M); - SaABI << DoLog; - } - - revng_assert(TheABIIR.verify(), "The ABI IR is invalid"); - - // Run the almighty ABI analyses - ABI.analyze(TheABIIR); - - // Find all the function calls that lead to results incoherent with the - // callees and register them - - std::set WrittenRegisters = TheABIIR.writtenRegisters(); - - IFS Summary; - if (Type == FunctionType::Regular) { - Summary = IFS::createRegular(std::move(GrandResult), - std::move(ABI), - std::move(FrameSizeAtCallSite), - std::move(BranchesType), - std::move(WrittenRegisters), - std::move(FakeReturns)); - } else { - Summary = IFS::createNoReturn(std::move(ABI), - std::move(FrameSizeAtCallSite), - std::move(BranchesType), - std::move(WrittenRegisters), - std::move(FakeReturns)); - } - findIncoherentFunctions(Summary); - - if (SaABI.isEnabled()) { - SaABI << "ABI analyses on " << Entry << " completed:\n"; - Summary.dump(M, SaABI); - SaABI << DoLog; - } - - return Summary; -} - -void Analysis::findIncoherentFunctions(const IFS &ABISummary) { - // TODO: do we need to take into account also all the registers used - // in the various function calls? - const IFS::LocalSlotVector &Slots = ABISummary.LocalSlots; - - for (const FunctionCall &FC : TheABIIR.incoherentCalls()) { - revng_log(SaFake, - FC.callee() << " (" << FC.callInstruction() << ") is fake."); - IncoherentFunctions.insert(FC.callee()); - } - - // Loop over all the function calls in this function - for (const auto &P : FrameSizeAtCallSite) { - const FunctionCall TheFunctionCall = P.first; - BasicBlock *Callee = TheFunctionCall.callee(); - - // We cannot perform any coherency check on indirect function calls - if (Callee == nullptr) - continue; - - // TODO: this is an hack, functions marked as fake should somehow be - // purged from CallsContext - if (TheCache->isFakeFunction(Callee)) - continue; - - // We might not have an entry, e.g., if they callee is noreturn - Optional Cache = TheCache->get(Callee); - if (Cache) { - const FunctionABI &CalleeSummary = (*Cache)->ABI; - - // Loop over all the slots being considered in this function - for (auto &Slot : Slots) { - if (not isCoherent(ABISummary.ABI, - CalleeSummary, - TheFunctionCall, - Slot)) { - IncoherentFunctions.insert(Callee); - break; - } - } - } - } -} - -bool Analysis::isCoherent(const FunctionABI &CallerSummary, - const FunctionABI &CalleeSummary, - FunctionCall TheFunctionCall, - IFS::LocalSlot Slot) const { - int32_t Offset = Slot.first.offset(); - BasicBlock *Callee = TheFunctionCall.callee(); - - switch (Slot.second) { - case LocalSlotType::UsedRegister: { - FunctionRegisterArgument FunctionArgument; - FunctionCallRegisterArgument FunctionCallArgument; - CalleeSummary.applyResults(FunctionArgument, Offset); - CallerSummary.applyResults(FunctionCallArgument, TheFunctionCall, Offset); - FunctionRegisterArgument CombinedArgument = FunctionArgument; - CombinedArgument.combine(FunctionCallArgument); - - if (CombinedArgument.isContradiction()) { - - if (SaFake.isEnabled()) { - SaFake << "Contradiction at "; - TheFunctionCall.dump(SaFake); - SaFake << " on argument "; - ASSlot::create(ASID::cpuID(), Offset).dump(M, SaFake); - SaFake << ": caller says is "; - FunctionCallArgument.dump(SaFake); - SaFake << ", while callee says is "; - FunctionArgument.dump(SaFake); - SaFake << ", marking " << Callee << " as fake." << DoLog; - } - - return false; - } - - FunctionReturnValue TheFunctionReturnValue; - FunctionCallReturnValue TheFunctionCallReturnValue; - CalleeSummary.applyResults(TheFunctionReturnValue, Offset); - CallerSummary.applyResults(TheFunctionCallReturnValue, - TheFunctionCall, - Offset); - FunctionReturnValue CombinedReturnValue = TheFunctionReturnValue; - CombinedReturnValue.combine(TheFunctionCallReturnValue); - - if (CombinedReturnValue.isContradiction()) { - - if (SaFake.isEnabled()) { - SaFake << "Contradiction at "; - TheFunctionCall.dump(SaFake); - SaFake << " on return value "; - ASSlot::create(ASID::cpuID(), Offset).dump(M, SaFake); - SaFake << ": caller says is "; - TheFunctionCallReturnValue.dump(SaFake); - SaFake << ", while callee says is "; - TheFunctionReturnValue.dump(SaFake); - SaFake << ", marking " << Callee << " as fake." << DoLog; - } - - return false; - } - - } break; - - case LocalSlotType::ForwardedArgument: - case LocalSlotType::ForwardedReturnValue: - case LocalSlotType::ExplicitlyCalleeSavedRegister: - break; - } - - return true; -} - -} // namespace Intraprocedural - -} // namespace StackAnalysis diff --git a/lib/StackAnalysis/Intraprocedural.h b/lib/StackAnalysis/Intraprocedural.h deleted file mode 100644 index 41cd4c651..000000000 --- a/lib/StackAnalysis/Intraprocedural.h +++ /dev/null @@ -1,528 +0,0 @@ -#pragma once - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include -#include -#include -#include -#include - -#include "llvm/ADT/SmallVector.h" - -#include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h" -#include "revng/Support/Debug.h" -#include "revng/Support/IRHelpers.h" -#include "revng/Support/MonotoneFramework.h" - -#include "ABIIR.h" -#include "Cache.h" -#include "Element.h" -#include "FunctionABI.h" -#include "IntraproceduralFunctionSummary.h" - -template -inline bool compareOptional(llvm::Optional LHS, llvm::Optional RHS) { - return LHS.hasValue() == RHS.hasValue() && (!LHS.hasValue() || *LHS == *RHS); -} - -namespace StackAnalysis { - -class Cache; - -/// \brief Copy of \p I, i.e., a container of classes with no copy constructor, -/// but having a .copy() method -template -inline T copyContainer(const T &I) { - T Result; - Result.reserve(I.size()); - for (auto &V : I) - Result.push_back(V.copy()); - - return Result; -} - -namespace Intraprocedural { - -/// \brief Result of the transfer function -/// -/// This class represents the result of the transfer function, it might simply -/// represent the result of the transfer functions starting from the initial -/// state or more sophisticated situations, e.g., function calls that have to be -/// handled by the intraprocedural part of the analysis. -class Interrupt { -public: - using vector = llvm::SmallVector; - using iterator = typename vector::iterator; - using const_iterator = typename vector::const_iterator; - using iterator_range = typename llvm::iterator_range; - using const_iterator_range = typename llvm::iterator_range; - -private: - bool ResultExtracted; - - BranchType::Values Type; - - Element Result; - vector RelatedBasicBlocks; - IntraproceduralFunctionSummary Summary; - -private: - Interrupt() : - ResultExtracted(false), - Type(BranchType::Invalid), - Result(Element::bottom()), - Summary(IntraproceduralFunctionSummary::bottom()) {} - - Interrupt(BranchType::Values Type, IntraproceduralFunctionSummary Summary) : - ResultExtracted(false), - Type(Type), - Result(Element::bottom()), - Summary(std::move(Summary)) {} - - Interrupt(Element Result, BranchType::Values Type, vector Successors = {}) : - ResultExtracted(false), - Type(Type), - Result(std::move(Result)), - RelatedBasicBlocks(Successors), - Summary(IntraproceduralFunctionSummary::bottom()) {} - - Interrupt(BranchType::Values Type, vector Successors) : - ResultExtracted(false), - Type(Type), - Result(Element::bottom()), - RelatedBasicBlocks(Successors), - Summary(IntraproceduralFunctionSummary::bottom()) {} - -public: - static Interrupt createInvalid() { return Interrupt(); }; - - static Interrupt createWithSuccessor(Element Result, - BranchType::Values Type, - llvm::BasicBlock *Successor) { - revng_assert(Type == BranchType::FakeFunctionCall - || Type == BranchType::HandledCall - || Type == BranchType::IndirectCall - || Type == BranchType::FakeFunctionReturn); - - return Interrupt(std::move(Result), Type, { Successor }); - } - - static Interrupt createWithSuccessors(Element Result, - BranchType::Values Type, - vector Successors) { - revng_assert(Type == BranchType::InstructionLocalCFG - || Type == BranchType::FunctionLocalCFG); - revng_assert(Successors.size() > 0); - - return Interrupt(std::move(Result), Type, Successors); - } - - static Interrupt create(Element Result, BranchType::Values Type) { - using namespace BranchType; - revng_assert(Type == Return || Type == IndirectTailCall - || Type == FakeFunction || Type == LongJmp || Type == Killer - || Type == Unreachable || Type == NoReturnFunction); - - return Interrupt(std::move(Result), Type); - } - - static Interrupt createUnhandledCall(llvm::BasicBlock *Callee) { - return Interrupt(BranchType::UnhandledCall, { Callee }); - } - - static Interrupt createSummary(IntraproceduralFunctionSummary Summary) { - switch (Summary.Type) { - case FunctionType::Regular: - return Interrupt(BranchType::RegularFunction, std::move(Summary)); - case FunctionType::NoReturn: - return Interrupt(BranchType::NoReturnFunction, std::move(Summary)); - case FunctionType::Fake: - return Interrupt(BranchType::FakeFunction, std::move(Summary)); - default: - revng_abort(); - } - return Interrupt(BranchType::RegularFunction, std::move(Summary)); - } - -public: - /// \brief True if this result has successors - bool hasSuccessors() const { - switch (Type) { - case BranchType::InstructionLocalCFG: - case BranchType::FunctionLocalCFG: - case BranchType::FakeFunctionCall: - case BranchType::FakeFunctionReturn: - case BranchType::HandledCall: - case BranchType::IndirectCall: - return true; - case BranchType::UnhandledCall: - case BranchType::Return: - case BranchType::BrokenReturn: - case BranchType::IndirectTailCall: - case BranchType::FakeFunction: - case BranchType::LongJmp: - case BranchType::Killer: - case BranchType::Unreachable: - return false; - case BranchType::Invalid: - case BranchType::RegularFunction: - case BranchType::NoReturnFunction: - revng_abort(); - } - - revng_abort(); - } - - BranchType::Values type() const { return Type; } - - bool isPartOfFinalResults() const { - // We bypass MonotoneFramework's collection of final results, since we're - // already collecting them in `Analysis::Returns` and we need to - // post-process them - return false; - } - - bool requiresInterproceduralHandling() const { - switch (Type) { - case BranchType::FakeFunction: - case BranchType::UnhandledCall: - case BranchType::RegularFunction: - case BranchType::NoReturnFunction: - return true; - case BranchType::InstructionLocalCFG: - case BranchType::FunctionLocalCFG: - case BranchType::FakeFunctionCall: - case BranchType::FakeFunctionReturn: - case BranchType::HandledCall: - case BranchType::IndirectCall: - case BranchType::Return: - case BranchType::BrokenReturn: - case BranchType::IndirectTailCall: - case BranchType::LongJmp: - case BranchType::Killer: - case BranchType::Unreachable: - return false; - case BranchType::Invalid: - revng_abort(); - } - - revng_abort(); - } - - Element &&extractResult() { - revng_assert(Type != BranchType::RegularFunction - and Type != BranchType::UnhandledCall); - - revng_assert(not ResultExtracted); - ResultExtracted = true; - return std::move(Result); - } - - llvm::BasicBlock *getCallee() const { - revng_assert(Type == BranchType::UnhandledCall); - revng_assert(RelatedBasicBlocks.size() == 1); - return RelatedBasicBlocks[0]; - } - - const IntraproceduralFunctionSummary &getFunctionSummary() { - // TODO: is it OK for fake functions to have summaries? - revng_assert(Type == BranchType::RegularFunction - || Type == BranchType::FakeFunction - || Type == BranchType::NoReturnFunction); - return Summary; - } - - const_iterator begin() { return RelatedBasicBlocks.begin(); } - const_iterator end() { return RelatedBasicBlocks.end(); } - size_t size() const { - revng_assert(hasSuccessors()); - return RelatedBasicBlocks.size(); - } - - void dump(const llvm::Module *M) const debug_function { dump(M, dbg); } - - template - void dump(const llvm::Module *M, T &Output) const { - Output << "Interrupt reason: " << BranchType::getName(Type) << "\n"; - switch (Type) { - case BranchType::InstructionLocalCFG: - case BranchType::FunctionLocalCFG: - case BranchType::FakeFunctionCall: - case BranchType::FakeFunctionReturn: - case BranchType::HandledCall: - case BranchType::IndirectCall: - Output << "Successors:"; - for (llvm::BasicBlock *BB : RelatedBasicBlocks) - Output << " " << getName(BB); - Output << "\n"; - Output << "Result:\n"; - Result.dump(M); - break; - case BranchType::UnhandledCall: - Output << "Unhandled call to " << getName(RelatedBasicBlocks[0]) << "\n"; - break; - case BranchType::RegularFunction: - Output << "Summary:\n"; - Summary.dump(M); - - break; - case BranchType::Return: - case BranchType::BrokenReturn: - case BranchType::NoReturnFunction: - case BranchType::IndirectTailCall: - case BranchType::FakeFunction: - case BranchType::LongJmp: - case BranchType::Killer: - Output << "Result:\n"; - Result.dump(M); - break; - case BranchType::Invalid: - case BranchType::Unreachable: - revng_abort(); - } - } -}; - -/// \brief Intraprocedural part of the stack analysis -class Analysis : public MonotoneFramework { -private: - // Label: llvm::BasicBlock * - // LatticeElement: Element - // Interrupt: Interrupt - // D (derived class): Analysis - // SuccessorsRange: Interrupt::const_iterator_range - // Visit: BreadthFirst - // DynamicGraph: true - using Base = MonotoneFramework; - -private: - llvm::BasicBlock *Entry; ///< The entry point of the current function - const llvm::Module *M; - const Cache *TheCache; ///< Reference to the Cache (for query purposes) - ASSlot ReturnAddressSlot; ///< Slot that contains the return address - GeneratedCodeBasicInfo *GCBI; - Element InitialState; ///< Empty Element with stack pointer initialized - int32_t SPIndex; ///< Offset of the stack pointer CSV - int32_t PCIndex; ///< Offset of the PC CSV - ABIFunction TheABIIR; ///< The ABI IR - - /// \brief Set of return addresses from fake function calls - std::set FakeReturnAddresses; - - /// \brief Branches list and classification - std::map BranchesType; - - std::map VariableContent; ///< Content of allocas - - /// This flag is set if the last time we interrupted the analysis was due to - /// an unhandled function call, which should then result in a cache hit - bool CacheMustHit; - - /// \brief Set of functions currently being analyzed, for recursion detection - /// purposes - const std::set &InProgressFunctions; - - /// \brief Record all call sites and the associated stack size - std::map> FrameSizeAtCallSite; - - /// \brief Called functions that have been found incoherent with the caller - std::set IncoherentFunctions; - - std::map ReturnCandidates; - - std::multimap FakeReturns; - -public: - Analysis(llvm::BasicBlock *Entry, - const Cache &TheCache, - GeneratedCodeBasicInfo *GCBI, - const std::set &InProgressFunctions) : - Base(Entry), - Entry(Entry), - M(getModule(Entry)), - TheCache(&TheCache), - ReturnAddressSlot(ASSlot::invalid()), - GCBI(GCBI), - InitialState(Element::bottom()), - TheABIIR(Entry), - InProgressFunctions(InProgressFunctions) { - - registerExtremal(Entry); - initialize(); - } - - bool isCSV(ASSlot Slot) const { - return (Slot.addressSpace() == ASID::cpuID() - and TheCache->isCSVIndex(Slot.offset())); - } - - void assertLowerThanOrEqual(const Element &A, const Element &B) const { - ::StackAnalysis::assertLowerThanOrEqual(A, B, getModule(Entry)); - } - - llvm::Optional handleEdge(const Element &Original, - llvm::BasicBlock *Source, - llvm::BasicBlock *Destination) const { - return llvm::Optional(); - } - - llvm::BasicBlock *entry() const { return Entry; } - - void resetCacheMustHit() { CacheMustHit = false; } - - bool cacheMustHit() const { return CacheMustHit; } - - /// \brief Reset the analysis with a new intial state - void initialize(); - - /// \brief Return the stack size of \p Result, if available - llvm::Optional stackSize(Element &Result) const { - Value StackPointer = Value::fromSlot(ASID::cpuID(), SPIndex); - ASID StackID = ASID::stackID(); - - // Save the value of the stack pointer for later - Value OldStackPointer = Result.load(StackPointer); - - llvm::Optional CallerStackSize; - if (const ASSlot *OldStackPointerSlot = OldStackPointer.directContent()) { - if (OldStackPointerSlot->addressSpace() != StackID) - return llvm::Optional(); - return -OldStackPointerSlot->offset(); - } else { - return llvm::Optional(); - } - } - - /// \brief Register the stack size at call site \p TheFunctionCall - /// - /// \return false if the stack size is different from the one that was - /// previously recorded, if any. - bool registerStackSizeAtCallSite(FunctionCall TheFunctionCall, - llvm::Optional StackSize) { - auto It = FrameSizeAtCallSite.find(TheFunctionCall); - if (It != FrameSizeAtCallSite.end()) { - if (not compareOptional(It->second, StackSize)) { - It->second = llvm::Optional(); - return false; - } - } else { - FrameSizeAtCallSite[TheFunctionCall] = StackSize; - } - - return true; - } - - /// \brief If available, return the registered size of the call site - /// \p Location - llvm::Optional frameSizeAt(FunctionCall Location) const { - auto It = FrameSizeAtCallSite.find(Location); - revng_assert(It != FrameSizeAtCallSite.end(), - "Location has never been registered"); - return It->second; - } - - /// \brief The almighty transfer function - Interrupt transfer(llvm::BasicBlock *BB); - - /// \brief The extremal value, i.e., the context of the analysis - Element extremalValue(llvm::BasicBlock *) const { - return InitialState.copy(); - } - - void dumpFinalState() const { - if (SaLog.isEnabled()) { - SaLog << "FinalResult:\n"; - FinalResult.dump(getModule(Entry), SaLog); - SaLog << DoLog; - } - } - - Interrupt::const_iterator_range - successors(llvm::BasicBlock *, Interrupt &I) const { - return llvm::make_range(I.begin(), I.end()); - } - - size_t successor_size(llvm::BasicBlock *, Interrupt &I) const { - if (I.hasSuccessors()) - return I.size(); - else - return 0; - } - - Interrupt createSummaryInterrupt() { revng_abort(); } - - Interrupt createNoReturnInterrupt() { - return Interrupt::createSummary(createSummary()); - } - - /// \brief Return the set of functions called by this function in an - /// incoherent way - /// - /// This function returns the set of basic blocks representing functions - /// called by the current function for which the information obtained about a - /// call site isn't compatible with the information obtained by analysing the - /// callee. - const std::set &incoherentFunctions() const { - return IncoherentFunctions; - } - -private: - std::pair finalize(); - - /// \brief Creates a summary for the current analysis ready to be wrapped in - /// an Interrupt - IntraproceduralFunctionSummary createSummary(); - - /// \brief Check whether the ABI analysis results for a slot of the function - /// and a call site are compatible - bool isCoherent(const FunctionABI &CallerSummary, - const FunctionABI &CalleeSummary, - FunctionCall TheFunctionCall, - IntraproceduralFunctionSummary::LocalSlot Slot) const; - - /// \brief Populate IncoherentFunctions - void - findIncoherentFunctions(const IntraproceduralFunctionSummary &ABISummary); - - /// \brief Part of the transfer function handling terminator instructions - Interrupt handleTerminator(llvm::Instruction *T, - Element &Result, - ABIIRBasicBlock &ABIBB); - - /// \brief Part of the transfer function handling function calls - Interrupt handleCall(llvm::Instruction *Caller, - llvm::BasicBlock *Callee, - MetaAddress ReturnAddress, - llvm::BasicBlock *ReturnFromCall, - Element &Result, - ABIIRBasicBlock &ABIBB); - - /// \return true if at least a branch is an indirect tail call - bool hasIndirectTailCall() const { - for (auto &P : BranchesType) - if (P.second == BranchType::IndirectTailCall) - return true; - return false; - } - - ASSlot slotFromCSV(llvm::User *U) const; -}; - -} // namespace Intraprocedural - -} // namespace StackAnalysis diff --git a/lib/StackAnalysis/IntraproceduralFunctionSummary.h b/lib/StackAnalysis/IntraproceduralFunctionSummary.h deleted file mode 100644 index 5ccf6225e..000000000 --- a/lib/StackAnalysis/IntraproceduralFunctionSummary.h +++ /dev/null @@ -1,266 +0,0 @@ -#pragma once - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include - -#include "Element.h" -#include "FunctionABI.h" - -extern Logger<> SaLog; - -namespace StackAnalysis { - -namespace LocalSlotType { - -enum Values { - UsedRegister, - ExplicitlyCalleeSavedRegister, - ForwardedArgument, - ForwardedReturnValue -}; - -inline const char *getName(Values Type) { - switch (Type) { - case UsedRegister: - return "UsedRegister"; - case ExplicitlyCalleeSavedRegister: - return "ExplicitlyCalleeSavedRegister"; - case ForwardedArgument: - return "ForwardedArgument"; - case ForwardedReturnValue: - return "ForwardedReturnValue"; - } - - revng_abort(); -} - -} // namespace LocalSlotType - -class IntraproceduralFunctionSummary { -public: - using LocalSlot = std::pair; - using LocalSlotVector = std::vector; - using IFS = IntraproceduralFunctionSummary; - using CallSiteStackSizeMap = std::map>; - using BranchesTypeMap = std::map; - using FakeReturnsMap = std::multimap; - -public: - FunctionType::Values Type; - Intraprocedural::Element FinalState; - FunctionABI ABI; - LocalSlotVector LocalSlots; - CallSiteStackSizeMap FrameSizeAtCallSite; - BranchesTypeMap BranchesType; - std::set WrittenRegisters; - FakeReturnsMap FakeReturns; - -public: - IntraproceduralFunctionSummary() : - Type(FunctionType::Invalid), - FinalState(Intraprocedural::Element::bottom()) {} - -private: - IntraproceduralFunctionSummary(FunctionType::Values Type) : - Type(Type), FinalState(Intraprocedural::Element::bottom()) {} - - IntraproceduralFunctionSummary(FunctionType::Values Type, - Intraprocedural::Element FinalState, - FunctionABI ABI, - CallSiteStackSizeMap FrameSizes, - BranchesTypeMap BranchesType, - std::set WrittenRegisters, - const FakeReturnsMap &FakeReturns) : - Type(Type), - FinalState(std::move(FinalState)), - ABI(std::move(ABI)), - FrameSizeAtCallSite(std::move(FrameSizes)), - BranchesType(std::move(BranchesType)), - WrittenRegisters(std::move(WrittenRegisters)), - FakeReturns(FakeReturns) { - - process(); - } - -public: - static IntraproceduralFunctionSummary createFake() { - return IntraproceduralFunctionSummary(FunctionType::Fake); - } - - static IntraproceduralFunctionSummary - createNoReturn(FunctionABI ABI, - CallSiteStackSizeMap FrameSizes, - BranchesTypeMap BranchesType, - std::set WrittenRegisters, - std::multimap FakeReturns) { - return IntraproceduralFunctionSummary(FunctionType::NoReturn, - Intraprocedural::Element::bottom(), - std::move(ABI), - std::move(FrameSizes), - std::move(BranchesType), - std::move(WrittenRegisters), - std::move(FakeReturns)); - } - - static IntraproceduralFunctionSummary - createRegular(Intraprocedural::Element FinalState, - FunctionABI ABI, - CallSiteStackSizeMap FrameSizes, - BranchesTypeMap BranchesType, - std::set WrittenRegisters, - std::multimap FakeReturns) { - return IntraproceduralFunctionSummary(FunctionType::Regular, - std::move(FinalState), - std::move(ABI), - std::move(FrameSizes), - std::move(BranchesType), - std::move(WrittenRegisters), - std::move(FakeReturns)); - } - - static IntraproceduralFunctionSummary bottom() { - return IntraproceduralFunctionSummary(); - } - - IFS copy() const { - IFS Result; - Result.Type = Type; - Result.FinalState = FinalState.copy(); - Result.ABI = ABI.copy(); - Result.LocalSlots = LocalSlots; - Result.FrameSizeAtCallSite = FrameSizeAtCallSite; - Result.BranchesType = BranchesType; - Result.WrittenRegisters = WrittenRegisters; - Result.FakeReturns = FakeReturns; - return Result; - } - - IntraproceduralFunctionSummary(const IFS &) = delete; - IntraproceduralFunctionSummary &operator=(const IFS &) = delete; - - IntraproceduralFunctionSummary(IFS &&) = default; - IntraproceduralFunctionSummary &operator=(IFS &&) = default; - - void dump(const llvm::Module *M) const debug_function { dump(M, dbg); } - - template - void dump(const llvm::Module *M, T &Output) const { - Output << "Type: " << FunctionType::getName(Type) << "\n"; - - Output << "FinalState:\n"; - FinalState.dump(M, Output); - Output << "\n"; - - Output << "ABI:\n"; - ABI.dump(M, Output); - Output << "\n"; - - Output << "Local slots (" << LocalSlots.size() << "):\n"; - for (const LocalSlot &Slot : LocalSlots) { - Output << " "; - Slot.first.dump(M, Output); - Output << ": " << LocalSlotType::getName(Slot.second) << "\n"; - } - } - -private: - void process() { - using namespace Intraprocedural; - using std::set; - - auto CPU = ASID::cpuID(); - auto SP0 = ASID::stackID(); - - const llvm::Module *M = nullptr; - int32_t CSVCount = std::numeric_limits::max(); - if (BranchesType.size() > 0) { - M = getModule(BranchesType.begin()->first); - CSVCount = std::distance(M->global_begin(), M->global_end()); - } - - auto IsValid = [CSVCount, CPU](ASSlot Slot) { - return Slot.addressSpace() == CPU and Slot.offset() <= CSVCount; - }; - - // Collect slots in the summary and those obtained by computing the ECS - // slots - set SlotsPool = FinalState.collectSlots(CSVCount); - ABI.collectLocalSlots(SlotsPool); - set CalleeSaved = FinalState.computeCalleeSavedSlots(); - - revng_assert(std::all_of(SlotsPool.begin(), SlotsPool.end(), IsValid)); - - for (ASSlot Slot : CalleeSaved) - SlotsPool.insert(Slot); - revng_assert(std::all_of(SlotsPool.begin(), SlotsPool.end(), IsValid)); - - set ForwardedArguments; - set ForwardedReturnValues; - - set Arguments; - set ReturnValues; - std::tie(Arguments, ReturnValues) = ABI.collectYesRegisters(); - - // Loop over return values to identify forwarded arguments (push rax; pop - // rdx) - // - // A forwarded argument is a register that seems to be a return value but it - // contains the initial value of another register, which appears to be an - // argument and whose value is on the stack. - for (int32_t Register : ReturnValues) { - ASSlot RegisterSlot = ASSlot::create(CPU, Register); - Value Content = FinalState.load(Value::fromSlot(RegisterSlot)); - if (const ASSlot *TheTag = Content.tag()) { - if (TheTag->addressSpace() == CPU and Register != TheTag->offset() - and Arguments.count(TheTag->offset()) != 0) { - // We have a return value containing the initial value of (another) - // argument - - // Check if we have this value in a stack slot too - if (FinalState.addressSpaceContainsTag(SP0, TheTag)) { - // OK, this is a forwarded argument - ForwardedArguments.insert(*TheTag); - ForwardedReturnValues.insert(ASSlot::create(CPU, Register)); - } - } - } - } - - // Sort out CPU slots by type - for (ASSlot Slot : SlotsPool) { - revng_assert(Slot.addressSpace() == CPU); - if (CalleeSaved.count(Slot) != 0) { - LocalSlots.emplace_back(Slot, - LocalSlotType::ExplicitlyCalleeSavedRegister); - } else if (ForwardedArguments.count(Slot) != 0) { - LocalSlots.emplace_back(Slot, LocalSlotType::ForwardedArgument); - } else if (ForwardedReturnValues.count(Slot) != 0) { - LocalSlots.emplace_back(Slot, LocalSlotType::ForwardedReturnValue); - } else { - LocalSlots.emplace_back(Slot, LocalSlotType::UsedRegister); - } - } - - for (const LocalSlot &Slot : LocalSlots) { - switch (Slot.second) { - case LocalSlotType::ExplicitlyCalleeSavedRegister: - // Drop from ABI analyses, pretend nothing happened - ABI.drop(Slot.first); - break; - - case LocalSlotType::ForwardedArgument: - case LocalSlotType::ForwardedReturnValue: - ABI.resetToUnknown(Slot.first); - break; - - case LocalSlotType::UsedRegister: - break; - } - } - } -}; - -} // namespace StackAnalysis diff --git a/lib/StackAnalysis/OVERVIEW.md b/lib/StackAnalysis/OVERVIEW.md deleted file mode 100644 index c00750a92..000000000 --- a/lib/StackAnalysis/OVERVIEW.md +++ /dev/null @@ -1,357 +0,0 @@ -This document describes from an high level point of view the stack analysis and -its components. - -# The `StackAnalysis` pass - -The `StackAnalysis` pass is where everything begins. Its `run` method does the -following: - -* It keeps an instance of the `Cache` class, which holds the results of the - analyses for each function analyzed so far. The `Cache` also identifies, for - each function call, where the return address is stored (i.e., the link - register or the top of the stack) and what's the most common place to store - the return address (an information that will be employed for functions that - have no direct calls). -* It identifies the function entry points. They are divided in two sets, one for - the entry points that are highly likely to represent a function (in - particular, those that are target of a direct call) and then the rest of - possible candidates. -* It runs the analysis on the first set of functions. -* It runs the analysis on the functions of the second set whose entry basic - block has not been identified as being part of a function of the first set. -* It collects the results of the analysis in `ResultsPool`. -* It produces the final version of the results contained in `ResultsPool`, - obtaining a `FunctionsSummary` object. - -# The `InterproceduralAnalysis` - -What we called *the analysis* is actually `InterproceduralAnalysis`. A run of -the `InterproceduralAnalysis` analyzes a single function and collects the -results of the analysis in a `ResultsPool` object. The `ResultsPool` contains -intermediate information that will be then merged into the final object exposed -by the analysis, i.e., `FunctionsSummary`. Note that the `Cache` is used -exclusively for handling function calls during the intraprocedural analysis, it -has nothing to do with the collection of the final results, which is handled by -`ResultsPool` and `FunctionsSummary`. - -To perform the analysis, `InterproceduralAnalysis` performs one or more -`Intraprocedural::Analysis`, starting from the entry point, but, if necessary, -analyzing all the functions in the call graph starting from the original entry -point. - -In practice, each time an `Intraprocedural::Analysis` meets a call site -targeting a function not already analyzed (i.e., not present in `Cache`), the -`Intraprocedural::Analysis` is suspended and the control is returned to the -`InterproceduralAnalysis` that will start a new `Intraprocedural::Analysis` for -the callee. The list of suspended `Intraprocedural::Analysis` is recorded on a -stack, whose top element is the currently running analysis. - -The `InterproceduralAnalysis` also handles recursion. In case the -`Intraprocedural::Analysis` has been suspended due to a uncached function call, -the interprocedural part will detect if the callee is a function already present -in the stack of the currently in-progress analyses and, in such case, it will -inject a temporary top entry in the cache. The intraprocedural analysis can now -proceed with a safe (and pessimistic) assumption. - -Once the analysis of the recursive function is terminated, as usual, it is -recorded in the cache. If the result is different from the cached one, the -analysis is restarted from scratch, but in this case the recursive call site -will use the more accurate information provided by the last analysis. This -process is repeated until a fixed point is reached, i.e., the result of the -analysis matches the one already present in the cache. - -# The intraprocedural analysis (stack analysis) - -The core goal of `Intraprocedural::Analysis` is to analyze how the registers are -used in a function and the extension of the function itself. - -In particular, the intraprocedural analysis can: - -* detect if a register is a callee-saved register in a function; -* if an indirect jump is a return instruction; -* if a function misbehaves with the stack and should therefore be considered a - "fake function", i.e., a function to inline in the callers; - -The intraprocedural analysis (in the `StackAnalysis::Intraprocedural` -namespace) is an instance of the `MonotoneFramework` class, and, as such, it has -a couple of interesting parts: the *lattice* and the *transfer function*. - -The intraprocedural analysis is a forward analysis. - -## The lattice - -The lattice is defined by the `Element` class. The `Element` class is basically -a container for a set of `AddressSpace`. An `Element` can have 0 or 3 address -spaces. - -The first `AddressSpace` (or "alias domain") represents the CPU state (`CPU`), -the second the global variables and the heap (`GLB`), while the third is for the -the stack frame of the current function (`SP0`). - -An `AddressSpace` tracks its content in the form of "slots" (`ASSlot`), i.e., a -pair of the identifier of an address space and an offset within it. For the -`CPU` address space, the offset represent the index of the CSV. - -The `GLB` address spaces is also used to track constants. This means that the -constant 42 will be represented as an `ASSlot` relative to `GLB` with offset 42 -(`GLB+42`). - -Note that our analyses currently ignore overlapping slots. Note also that -overlapping slots are not possible in the `CPU` address space (CSVs never -alias). - -An `AddressSpace` associates to each slot a `Value`. A `Value` is composed by -two `ASSlot` fields: a "direct content", i.e., the content of the slot in a -certain program point (according to our analysis) and a "tag". A tag represents -the fact that we are not able to track the actual content of that slot -statically, but we know that it contains the value that another slot contained -at the entry of the function. The tag is useful to represent, e.g., the -information that a certain stack slot (where the value of a callee-saved -register is saved) contains the initial value of a register, or that an indirect -jump is targeting a `Value` representing the initial value, e.g., of the link -register. In the latter case, we basically proved that the indirect jump is -actually a return instruction. - -The intraprocedural analysis associates to each SSA value an element of the -lattice. However, only the lattice element at the end of the basic block -currently being processed is of our interest (so that it can be propagated to -its successors). - -## The transfer function - -The transfer function of the intraprocedural analysis handles mainly the -following types of instructions: - -* `StoreInst`: a store has an address and a value to store, they will be both - associated to a `Value`. If the address `Value` has a direct content, the - `AddressSpace` associated to the direct content `ASSlot` will be updated at - the appropriate offset. -* `LoadInst`: if the `Value` associated to the pointer operand has a direct - content, we will look up in the corresponding address space at the - corresponding offset if have recorded a `Value`. If so, the result is that - `Value`. Otherwise this means that we are not aware of any store instruction - targeting that address, therefore, the resulting `Value` won't have a direct - content but just a tag representing the loaded address. -* `TerminatorInst`: terminator instructions go through a classification - depending on the context in which they are performed. For instance, an - indirect jump might be detected as a return instruction if it's jumping to a - `Value` tagged with the link register `ASSlot` *and* the stack is no higher - than how it was at entry of the function. - - A `TerminatorInst` can also represent a function call, in such case, its - callee (if not indirect) is looked up into the `Cache`, if it's available the - result of its analysis replace the current state of the `AddressSpace`s, - otherwise the intraprocedural analysis is suspended and the control is - returned to the interprocedural part as described above. - -Other basic instructions are handled in the straightforward way, e.g., addition. - -# The ABI analysis - -As part of the finalization of the results of the intraprocedural analysis -(`Analysis::createSummary`), the ABI analysis is performed. The goal of the ABI -analysis is to detect arguments and return values of function and function -calls. - -## The ABI IR - -The ABI analysis is performed on a custom IR, the ABI IR, which is produced by -the intraprocedural analysis during its execution. The main motivating reason -for having this IR is to facilitate debugging and, most importantly, being able -to perform backward analyses easily. - -The ABI IR is quite simple: the `ABIIRFunction` is a container of -`ABIIRBasicBlock`s which in turn are containers for `ABIIRInstruction`s. Each -basic block has links to its successors and predecessors. - -An `ABIIRInstruction` can be of the following types: - -* `Load`: a read from an `ASSlot`; -* `Store`: a write to an `ASSlot` (what is being written, is of no interest); -* `DirectCall`: a function call for which a result of a previously run ABI - analysis is available. -* `IndirectCall`: a function call about which nothing is known. - -## The analyses - -The `FunctionABI` class is responsible for performing all the analyses -concerning the ABI. In particular, the `analyze` method performs two sets of -analyses: the first set are forward, while the second one is backward. - -Each set can be further divided into two groups: the analyses concerning the -*function* itself and analyses concerning the *function calls*. - -Each function-level analysis starts with a `Default` instance, then, each time a -memory access to a certain CSV is met, the `Default` instance is cloned and -associated to that CSV, whose analysis then proceeds independently. - -The same holds for function call-level analyses, with the distinction that the -same "lazy" instantiation of a set of analyses happens also each time a call -site is met. - -The set of function-level analyses contain all the function call-level analyses -too so that their result can be used by the calling function performing function -call-level analyses (as if they were inlined). - -Each analysis is wrapped in an `Inhibitor` class, which, as the name suggests, -is used to inhibit the wrapped analysis from applying the transfer function -while walking the IR. Function-level analyses are never inhibited. Function -call-level analyses, instead, start as inhibited and, once the corresponding -function call is met, they are enabled. This helps us to simulate the beginning -of the analysis in that point. The transfer function of a function call analysis -reaching for the second time the function call is the unknown function call -transfer function. - -For the list of forward, backward, function and function call analyses, consult -the source code and the `.dot` files used to generate them. - -# Merging the results - -At the end of all of the analyses, the `ResultsPool` object will contain a -summary of all the recovered information such as the basic block composing a -function, the type of function, the status of each registers in terms of being -an argument or a return value for each function and for each function call and -so on. - -These information, and, specifically the last two pieces of information, have to -be merged together in order to produce more accurate information or identify -contradictions. This step is performed in `ResultsPool::finalize`. - -Basically the idea is that if for a certain register we have a `Yes` from a call -site and a `NoOrDead` from the function itself, we will produce as a final -information for the call site `Dead`. We will produce `Dead` for the function -too only in case *all* of the call sites agree. - -# Ad-hoc handling of peculiar situations - -For various reasons, in part concerning our code generation pipeline and in part -due to certain practices in compiler backends, we have to handle certain -situations in an ad-hoc way to avoid mistakes. - -In the following we will discuss the situations we currently handle. - -## Fake functions - -Consider the following example of ARM code: - - _start: - push {lr} - bl prologue - ldr r0, [r0] - b epilogue - - prologue: - push {r0} - push {r1} - bx lr - - epilogue: - pop {r1} - pop {r0} - pop {lr} - bx lr - -The compiler (or the developer) decided to outline the function prologue and -epilogue, likely for code size reduction reasons. In this situation we don't -really want to consider `prologue` and `epilogue` as standalone functions, for -two reasons: they manipulate the stack in weird ways and prevent us from -identifying callee-saved registers. - -The jump to the epilogue is not a problem since it will automatically considered -part of each function jumping there. On the other hand, we need to make sure -that we can correctly identify `prologue` as a *fake* function, and, therefore, -inline it in the caller. - -To do this, we can note that at the end of the "function" the stack is higher -than it was at the beginning. No sane function call would allow this. Therefore, -we mark `prologue` as a fake function. - -Note, on the other hand, than having a stack *lower* than it was at the -beginning is allowed, since certain caller conventions mandate to the callee the -cleanup of stack arguments (e.g., the Windows PASCAL calling convention). - -As a consequence, when we analyze a terminator during the intraprocedural -analysis (`handleTerminator` method), when we meet an instruction that jumps to -the initial content of the link register, we understand it's a return, but if we -see that the stack is higher than it initially was, we mark the functions as -fake and resume the analysis of the caller (if any), in which we will inline the -function call. - -Consider now the following a variation of the previous snippet: - - _start: - push {lr} - add sp,sp,-8 - bl prologue - ldr r0, [r0] - b epilogue - - prologue: - str r1,[sp,0] - str r0,[sp,4] - bx lr - - epilogue: - pop {r1} - pop {r0} - pop {lr} - bx lr - -In this case, the stack pointer is not touched by the `prologue` -function. Therefore, the previous criteria is not effective. In this case we -observe another fact: the `prologue` function writes in `SP0+0` and `SP0+4`, -which seem to be stack argument. This is fine, however we keep track of -this. The next thing we observe is that the same stack slots are *read* by the -caller (in the `epilogue` basic block). Under our assumptions, this is not -allowed, since no return value is passed (directly) on the stack and stack -arguments are no longer valid after the function returns. Therefore, we mark -the called function as fake. - -This analysis is performed by the `StackAnalysis::IncoherentCallsAnalysis` -analysis, which is performed on the ABI IR. The analysis is triggered by the -`findIncoherentFunctions` function in the `createSummary` method of the -intraprocedural analysis, after the ABI analysis has been run. - -## Forwarded arguments - -Consider the following snippet of x86-64 assembly: - - push_pop: - push rax - pop rdx - ret - -This code is sometimes emitted by the compiler with the only goal of growing and -decreasing the stack height. The `rax` and `rdx` registers do not contain -anything meaningful. - -The problem with this snippet is that, according to our analyses, `rax` is an -argument and `rdx` is a return value, while, obviously this is not the case. - -To detect this situation, the `IntraproceduralFunctionSummary`, which holds the -final result of the intraprocedural analysis of a functions, has a `process` -method the pattern matches it: if a return value is tagged with the initial -value of different register and that value is also stored in a stack slot, we -mark it as a forwarded argument. - -Statements about such registers in terms of being arguments/return values will -be weakened. - -## Identity loads - -Identity loads are a particular type of load instructions. Consider the -following x86-64 pseudo code: - - a = rax & 0xffff0000 - b = 0xaaaa - rax = a | b - -This snippet is the result of writing only the lowest 16 bits of `rax` (with -`0xAAAA`), however, from our point of view we have a read of `rax` before any -write and would, therefore, consider it an argument. This load is an identity -load, a load whose value will end up as is in itself. - -Identity loads are ignored completely. They are not even part of the ABI IR. - -The `Cache` is in charge to identify and keep track of identity loads -(`Cache::identifyIdentityLoads`). diff --git a/lib/StackAnalysis/RETURNS.md b/lib/StackAnalysis/RETURNS.md deleted file mode 100644 index 17538ef18..000000000 --- a/lib/StackAnalysis/RETURNS.md +++ /dev/null @@ -1,17 +0,0 @@ -# Approach - -1. Identify all the return candidates [DONE] - * Indirect jumps to the return address - * Indirect tail calls, i.e., indirect jumps to unknown addresses with SP >= 0 -2. Go through all the candidates and elect a return stack pointer - * Mark all non-complaint ones as broken returns/indirect tail calls. They are *not* returns. [DONE] - * Combine the result associated to each propore return/indirect tail call, that's the grand result. Mark the ABIIRBasicBlock as return. [DONE] - - a. If no proper stack pointer can be elected, but all the return points agree on a single SP <0, mark the function as outlined. - b. If no proper stack pointer can be elected, mark the function as noreturn - c. If a proper stack pointer has been elected, mark the function as regular. - -# TODOs - -* Indirect tail calls must become longjmps, or longjmps should be treated as indirect tail calls - diff --git a/lib/StackAnalysis/RegisterArgumentsOfFunctionCall.dot b/lib/StackAnalysis/RegisterArgumentsOfFunctionCall.dot deleted file mode 100644 index ff7a456f2..000000000 --- a/lib/StackAnalysis/RegisterArgumentsOfFunctionCall.dot +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is distributed under the MIT License. See LICENSE.md for details. -# - -digraph RegisterArgumentsOfFunctionCall { - Bottom; - Maybe [peripheries=2]; - Yes; - Unknown; - - # Lattice - Bottom->Yes; - Bottom->Maybe; - Yes->Unknown; - Maybe->Unknown; - - # Transfer functions - Maybe->Yes [label="Write"]; - Maybe->Unknown [label="Read"]; - Maybe->Unknown [label="UnknownFunctionCall"]; - Maybe->Unknown [label="TheCall"]; - - # Prevent return values to become arguments right away - Maybe->Maybe [label="ReturnFromYes"]; -} diff --git a/lib/StackAnalysis/StackAnalysis.cpp b/lib/StackAnalysis/StackAnalysis.cpp index a36ce7527..7297f0f37 100644 --- a/lib/StackAnalysis/StackAnalysis.cpp +++ b/lib/StackAnalysis/StackAnalysis.cpp @@ -60,9 +60,6 @@ #include "revng/Support/MetaAddress.h" #include "ABIAnalyses/ABIAnalysis.h" -#include "Cache.h" -#include "InterproceduralAnalysis.h" -#include "Intraprocedural.h" using llvm::ArrayRef; using llvm::BasicBlock; @@ -82,7 +79,6 @@ using GCBI = GeneratedCodeBasicInfo; using namespace llvm::cl; static Logger<> CFEPLog("cfep"); -static Logger<> ClobberedLog("clobbered"); static Logger<> StackAnalysisLog("stackanalysis"); struct BasicBlockNodeData { @@ -114,19 +110,11 @@ struct llvm::DOTGraphTraits namespace StackAnalysis { -const std::set EmptyCSVSet; - char StackAnalysis::ID = 0; using RegisterABI = RegisterPass; static RegisterABI Y("abi-analysis", "ABI Analysis Pass", true, true); -static opt ABIAnalysisOutputPath("abi-analysis-output", - desc("Destination path for the " - "ABI Analysis Pass"), - value_desc("path"), - cat(MainCategory)); - static opt CallGraphOutputPath("cg-output", desc("Dump to disk the recovered " "call graph."), @@ -479,371 +467,6 @@ CFEPAnalyzer::CFEPAnalyzer(llvm::Module &M, } } -template -static model::RegisterState::Values -toRegisterState(RegisterArgument RA) { - switch (RA.value()) { - case RegisterArgument::NoOrDead: - return model::RegisterState::NoOrDead; - case RegisterArgument::Maybe: - return model::RegisterState::Maybe; - case RegisterArgument::Yes: - return model::RegisterState::Yes; - case RegisterArgument::Dead: - return model::RegisterState::Dead; - case RegisterArgument::Contradiction: - return model::RegisterState::Contradiction; - case RegisterArgument::No: - return model::RegisterState::No; - } - - revng_abort(); -} - -static model::RegisterState::Values toRegisterState(FunctionReturnValue RV) { - switch (RV.value()) { - case FunctionReturnValue::No: - return model::RegisterState::No; - case FunctionReturnValue::NoOrDead: - return model::RegisterState::NoOrDead; - case FunctionReturnValue::YesOrDead: - return model::RegisterState::YesOrDead; - case FunctionReturnValue::Maybe: - return model::RegisterState::Maybe; - case FunctionReturnValue::Contradiction: - return model::RegisterState::Contradiction; - } - - revng_abort(); -} - -static model::RegisterState::Values -toRegisterState(FunctionCallReturnValue RV) { - switch (RV.value()) { - case FunctionCallReturnValue::No: - return model::RegisterState::No; - case FunctionCallReturnValue::NoOrDead: - return model::RegisterState::NoOrDead; - case FunctionCallReturnValue::YesOrDead: - return model::RegisterState::YesOrDead; - case FunctionCallReturnValue::Yes: - return model::RegisterState::Yes; - case FunctionCallReturnValue::Dead: - return model::RegisterState::Dead; - case FunctionCallReturnValue::Maybe: - return model::RegisterState::Maybe; - case FunctionCallReturnValue::Contradiction: - return model::RegisterState::Contradiction; - } - - revng_abort(); -} - -void commitToModel(GeneratedCodeBasicInfo &GCBI, - Function *F, - const FunctionsSummary &Summary, - model::Binary &TheBinary); - -void commitToModel(GeneratedCodeBasicInfo &GCBI, - Function *F, - const FunctionsSummary &Summary, - model::Binary &TheBinary) { - using namespace model; - - // - // Create all the model::Function - // - for (const auto &[Entry, FunctionSummary] : Summary.Functions) { - if (Entry == nullptr) - continue; - - // Get the entry point address - MetaAddress EntryPC = getBasicBlockPC(Entry); - revng_assert(EntryPC.isValid()); - - model::Function &Function = Binary.Functions[EntryPC]; - - // Assign a name - - using FT = model::FunctionType::Values; - Function.Type = static_cast(FunctionSummary.Type); - - if (Function.Type == model::FunctionType::Fake) - continue; - - // Build the function prototype - auto NewType = makeType(); - auto &FunctionType = *llvm::cast(NewType.get()); - { - auto ArgumentsInserter = FunctionType.Arguments.batch_insert(); - auto ReturnValuesInserter = FunctionType.ReturnValues.batch_insert(); - for (auto &[CSV, FRD] : FunctionSummary.RegisterSlots) { - auto RegisterID = ABIRegister::fromCSVName(CSV->getName(), GCBI.arch()); - if (RegisterID == Register::Invalid or CSV == GCBI.spReg()) - continue; - - llvm::Type *CSVType = CSV->getType()->getPointerElementType(); - auto CSVSize = CSVType->getIntegerBitWidth() / 8; - NamedTypedRegister TR(RegisterID); - TR.Type = { - TheBinary.getPrimitiveType(PrimitiveTypeKind::Generic, CSVSize), {} - }; - - if (model::RegisterState::shouldEmit(toRegisterState(FRD.Argument))) - ArgumentsInserter.insert(TR); - - if (model::RegisterState::shouldEmit(toRegisterState(FRD.ReturnValue))) - ReturnValuesInserter.insert(TR); - - // TODO: populate preserved registers - } - } - - Function.Prototype = TheBinary.recordNewType(std::move(NewType)); - } - - // - // Populate the CFG - // - for (const auto &[Entry, FunctionSummary] : Summary.Functions) { - if (Entry == nullptr) - continue; - MetaAddress EntryPC = getBasicBlockPC(Entry); - - auto It = TheBinary.Functions.find(EntryPC); - if (It == TheBinary.Functions.end()) - continue; - - model::Function &Function = *It; - - if (Function.Type == model::FunctionType::Fake) - continue; - - auto MakeEdge = [](MetaAddress Destination, FunctionEdgeType::Values Type) { - FunctionEdge *Result = nullptr; - if (FunctionEdgeType::isCall(Type)) - Result = new CallEdge(Destination, Type); - else - Result = new FunctionEdge(Destination, Type); - return UpcastablePointer(Result); - }; - - // Handle the situation in which we found no basic blocks at all - if (Function.Type == model::FunctionType::NoReturn - and FunctionSummary.BasicBlocks.size() == 0) { - auto &EntryNodeSuccessors = Function.CFG[EntryPC].Successors; - auto Edge = MakeEdge(MetaAddress::invalid(), FunctionEdgeType::LongJmp); - EntryNodeSuccessors.insert(Edge); - } - - for (auto &[BB, Branch] : FunctionSummary.BasicBlocks) { - // Remap BranchType to FunctionEdgeType - namespace FET = FunctionEdgeType; - FET::Values EdgeType = FET::Invalid; - - switch (Branch) { - case BranchType::Invalid: - case BranchType::FakeFunction: - case BranchType::RegularFunction: - case BranchType::NoReturnFunction: - case BranchType::UnhandledCall: - revng_abort(); - break; - - case BranchType::InstructionLocalCFG: - continue; - - default: - break; - } - - // Identify Source address - auto [Source, Size] = getPC(BB->getTerminator()); - Source += Size; - revng_assert(Source.isValid()); - - // Identify Destination address - llvm::BasicBlock *JumpTargetBB = GCBI.getJumpTargetBlock(BB); - MetaAddress JumpTargetAddress = GCBI.getPCFromNewPC(JumpTargetBB); - model::BasicBlock &CurrentBlock = Function.CFG[JumpTargetAddress]; - CurrentBlock.End = Source; - auto SuccessorsInserter = CurrentBlock.Successors.batch_insert(); - - llvm::BasicBlock *Successor = BB->getSingleSuccessor(); - llvm::StringRef SymbolName; - - MetaAddress Destination = MetaAddress::invalid(); - if (Successor != nullptr) - Destination = getBasicBlockPC(Successor); - - if (Destination.isValid()) { - revng_assert(not JumpTargetBB->empty()); - auto *NewPCCall = getCallTo(&*Successor->begin(), "newpc"); - revng_assert(NewPCCall != nullptr); - - // Extract symbol name if any - auto *SymbolNameValue = NewPCCall->getArgOperand(4); - if (not isa(SymbolNameValue)) { - llvm::Value *SymbolNameString = NewPCCall->getArgOperand(4); - SymbolName = extractFromConstantStringPtr(SymbolNameString); - revng_assert(SymbolName.size() != 0); - } - } - - switch (Branch) { - case BranchType::Invalid: - case BranchType::FakeFunction: - case BranchType::RegularFunction: - case BranchType::NoReturnFunction: - case BranchType::UnhandledCall: - case BranchType::InstructionLocalCFG: - revng_abort(); - break; - - case BranchType::FunctionLocalCFG: - EdgeType = FET::DirectBranch; - break; - - case BranchType::FakeFunctionCall: - EdgeType = FET::FakeFunctionCall; - break; - - case BranchType::FakeFunctionReturn: - EdgeType = FET::FakeFunctionReturn; - break; - - case BranchType::HandledCall: - EdgeType = FET::FunctionCall; - break; - - case BranchType::IndirectCall: - if (SymbolName.size() == 0) - EdgeType = FET::IndirectCall; - else - EdgeType = FET::FunctionCall; - break; - - case BranchType::Return: - EdgeType = FET::Return; - break; - - case BranchType::BrokenReturn: - EdgeType = FET::BrokenReturn; - break; - - case BranchType::IndirectTailCall: - EdgeType = FET::IndirectTailCall; - break; - - case BranchType::LongJmp: - EdgeType = FET::LongJmp; - break; - - case BranchType::Killer: - EdgeType = FET::Killer; - break; - - case BranchType::Unreachable: - EdgeType = FET::Unreachable; - break; - } - - if (EdgeType == FET::DirectBranch) { - // Handle direct branch - auto Successors = GCBI.getSuccessors(BB); - for (const MetaAddress &Destination : Successors.Addresses) - SuccessorsInserter.insert(MakeEdge(Destination, EdgeType)); - - } else if (EdgeType == FET::FakeFunctionReturn) { - // Handle fake function return - auto [First, Last] = FunctionSummary.FakeReturns.equal_range(BB); - revng_assert(First != Last); - for (const auto &[_, Destination] : make_range(First, Last)) - SuccessorsInserter.insert(MakeEdge(Destination, EdgeType)); - - } else if (FunctionEdgeType::isCall(EdgeType)) { - // Record the edge in the CFG - auto TempEdge = MakeEdge(Destination, EdgeType); - const auto &Result = SuccessorsInserter.insert(TempEdge); - auto *Edge = llvm::cast(Result.get()); - - const auto IDF = TheBinary.ImportedDynamicFunctions; - bool IsDynamicCall = (not SymbolName.empty() - and IDF.count(SymbolName.str()) != 0); - if (IsDynamicCall) { - // It's a dynamic function call - revng_assert(EdgeType == model::FunctionEdgeType::FunctionCall); - Edge->Destination = MetaAddress::invalid(); - Edge->DynamicFunction = SymbolName.str(); - - // The prototype is implicitly the one of the callee - revng_assert(not Edge->Prototype.isValid()); - } else if (Destination.isValid()) { - // It's a simple direct function call - revng_assert(EdgeType == model::FunctionEdgeType::FunctionCall); - - // The prototype is implicitly the one of the callee - revng_assert(not Edge->Prototype.isValid()); - } else { - // It's an indirect call: forge a new prototype - auto NewType = makeType(); - auto &CallType = *llvm::cast(NewType.get()); - { - auto ArgumentsInserter = CallType.Arguments.batch_insert(); - auto ReturnValuesInserter = CallType.ReturnValues.batch_insert(); - bool Found = false; - for (const FunctionsSummary::CallSiteDescription &CSD : - FunctionSummary.CallSites) { - if (not CSD.Call->isTerminator() or CSD.Call->getParent() != BB) - continue; - - revng_assert(not Found); - Found = true; - for (auto &[CSV, FCRD] : CSD.RegisterSlots) { - auto RegisterID = ABIRegister::fromCSVName(CSV->getName(), - GCBI.arch()); - if (RegisterID == model::Register::Invalid - or CSV == GCBI.spReg()) - continue; - - llvm::Type *CSVType = CSV->getType()->getPointerElementType(); - auto CSVSize = CSVType->getIntegerBitWidth() / 8; - NamedTypedRegister TR(RegisterID); - TR.Type = { - TheBinary.getPrimitiveType(model::PrimitiveTypeKind::Generic, - CSVSize), - {} - }; - - auto ArgumentState = toRegisterState(FCRD.Argument); - if (model::RegisterState::shouldEmit(ArgumentState)) - ArgumentsInserter.insert(TR); - - auto ReturnValueState = toRegisterState(FCRD.ReturnValue); - if (model::RegisterState::shouldEmit(ReturnValueState)) - ReturnValuesInserter.insert(TR); - - // TODO: populate preserved registers and FinalStackOffset - } - } - revng_assert(Found); - } - - Edge->Prototype = TheBinary.recordNewType(std::move(NewType)); - } - } else { - // Handle other successors - llvm::BasicBlock *Successor = BB->getSingleSuccessor(); - - // Record the edge in the CFG - SuccessorsInserter.insert(MakeEdge(Destination, EdgeType)); - } - } - } - - revng_check(TheBinary.verify(true)); -} - static UpcastablePointer buildPrototype(GeneratedCodeBasicInfo &GCBI, model::Binary &Binary, @@ -2301,196 +1924,7 @@ bool StackAnalysis::runOnModule(Module &M) { // Finalize model finalizeModel(GCBI, Functions, ABIRegisters, Properties, Binary); - // Initialize the cache where all the results will be accumulated - Cache TheCache(&F, &GCBI); - - // Pool where the final results will be collected - ResultsPool Results; - - // First analyze all the `Force`d functions (i.e., with an explicit direct - // call) - for (CFEP &Function : Functions) { - if (Function.Force) { - auto &GCBI = getAnalysis().getGCBI(); - InterproceduralAnalysis SA(TheCache, GCBI); - SA.run(Function.Entry, Results); - } - } - - // Now analyze all the remaining candidates which are not already part of - // another function - std::set Visited = Results.visitedBlocks(); - for (CFEP &Function : Functions) { - if (not Function.Force and Visited.count(Function.Entry) == 0) { - auto &GCBI = getAnalysis().getGCBI(); - InterproceduralAnalysis SA(TheCache, GCBI); - SA.run(Function.Entry, Results); - } - } - - for (CFEP &Function : Functions) { - using IFS = IntraproceduralFunctionSummary; - BasicBlock *Entry = Function.Entry; - llvm::Optional Cached = TheCache.get(Entry); - revng_assert(Cached or TheCache.isFakeFunction(Entry)); - - // Has this function been analyzed already? If so, only now we register it - // in the ResultsPool. - FunctionType::Values Type; - if (TheCache.isFakeFunction(Entry)) - Type = FunctionType::Fake; - else if (TheCache.isNoReturnFunction(Entry)) - Type = FunctionType::NoReturn; - else - Type = FunctionType::Regular; - - // Regular functions need to be composed by at least a basic block - if (Cached) { - const IFS *Summary = *Cached; - if (Type == FunctionType::Regular) - revng_assert(Summary->BranchesType.size() != 0); - - Results.registerFunction(Entry, Type, Summary); - } else { - Results.registerFunction(Entry, Type, nullptr); - } - } - - GrandResult = Results.finalize(&M, &TheCache); - - if (ClobberedLog.isEnabled()) { - for (auto &P : GrandResult.Functions) { - ClobberedLog << getName(P.first) << ":"; - for (const llvm::GlobalVariable *CSV : P.second.ClobberedRegisters) - ClobberedLog << " " << CSV->getName().data(); - ClobberedLog << DoLog; - } - } - - if (StackAnalysisLog.isEnabled()) { - std::stringstream Output; - GrandResult.dump(&M, Output); - TextRepresentation = Output.str(); - revng_log(StackAnalysisLog, TextRepresentation); - } - - revng_log(PassesLog, "Ending StackAnalysis"); - - if (ABIAnalysisOutputPath.getNumOccurrences() == 1) { - std::ofstream Output; - serialize(pathToStream(ABIAnalysisOutputPath, Output)); - } - -#if 0 - commitToModel(GCBI, &F, GrandResult, Binary); -#endif - return false; } -void StackAnalysis::serializeMetadata(Function &F, - GeneratedCodeBasicInfo &GCBI) { - using namespace llvm; - - const FunctionsSummary &Summary = GrandResult; - - LLVMContext &Context = getContext(&F); - QuickMetadata QMD(Context); - - // Temporary data structure so we can set all the `revng.func.member.of` in a - // single shot at the end - std::map> MemberOf; - - // Loop over all the detected functions - for (const auto &P : Summary.Functions) { - BasicBlock *Entry = P.first; - const FunctionsSummary::FunctionDescription &Function = P.second; - - if (Entry == nullptr or Function.BasicBlocks.size() == 0) - continue; - - MetaAddress EntryPC = getBasicBlockPC(Entry); - - // - // Add `revng.func.entry`: - // { - // name, - // address, - // type, - // { clobbered csv, ... }, - // { { csv, argument, return value }, ... } - // } - // - auto *TypeMD = QMD.get(FunctionType::getName(Function.Type)); - - // Clobbered registers metadata - std::vector ClobberedMDs; - for (GlobalVariable *ClobberedCSV : Function.ClobberedRegisters) { - if (not GCBI.isServiceRegister(ClobberedCSV)) - ClobberedMDs.push_back(QMD.get(ClobberedCSV)); - } - - // Register slots metadata - std::vector SlotMDs; - for (auto &P : Function.RegisterSlots) { - if (GCBI.isServiceRegister(P.first)) - continue; - - auto *CSV = QMD.get(P.first); - auto *Argument = QMD.get(P.second.Argument.valueName()); - auto *ReturnValue = QMD.get(P.second.ReturnValue.valueName()); - SlotMDs.push_back(QMD.tuple({ CSV, Argument, ReturnValue })); - } - - // Create revng.func.entry metadata - MDTuple *FunctionMD = QMD.tuple({ QMD.get(getName(Entry)), - QMD.get(GCBI.toConstant(EntryPC)), - TypeMD, - QMD.tuple(ClobberedMDs), - QMD.tuple(SlotMDs) }); - Entry->getTerminator()->setMetadata("revng.func.entry", FunctionMD); - - // - // Create func.call - // - for (const FunctionsSummary::CallSiteDescription &CallSite : - Function.CallSites) { - Instruction *Call = CallSite.Call; - - // Register slots metadata - std::vector SlotMDs; - for (auto &P : CallSite.RegisterSlots) { - if (GCBI.isServiceRegister(P.first)) - continue; - - auto *CSV = QMD.get(P.first); - auto *Argument = QMD.get(P.second.Argument.valueName()); - auto *ReturnValue = QMD.get(P.second.ReturnValue.valueName()); - SlotMDs.push_back(QMD.tuple({ CSV, Argument, ReturnValue })); - } - - Call->setMetadata("func.call", QMD.tuple(QMD.tuple(SlotMDs))); - } - - // - // Create revng.func.member.of - // - - // Loop over all the basic blocks composing the function - for (const auto &P : Function.BasicBlocks) { - BasicBlock *BB = P.first; - BranchType::Values Type = P.second; - - auto *Pair = QMD.tuple({ FunctionMD, QMD.get(getName(Type)) }); - - // Register that this block is associated to this function - MemberOf[BB->getTerminator()].push_back(Pair); - } - } - - // Apply `revng.func.member.of` - for (auto &P : MemberOf) - P.first->setMetadata("revng.func.member.of", QMD.tuple(P.second)); -} - } // namespace StackAnalysis diff --git a/lib/StackAnalysis/UsedArgumentsOfFunction.dot b/lib/StackAnalysis/UsedArgumentsOfFunction.dot deleted file mode 100644 index e9f75f1dc..000000000 --- a/lib/StackAnalysis/UsedArgumentsOfFunction.dot +++ /dev/null @@ -1,20 +0,0 @@ -# -# This file is distributed under the MIT License. See LICENSE.md for details. -# - -# This analysis works both for registers and stack slots - -digraph UsedArgumentsOfFunction { - Unknown; - Maybe [peripheries=2]; - Yes; - - # Lattice - Unknown->Maybe; - Maybe->Yes; - - # Transfer functions - Maybe->Yes [label="Read"]; - Maybe->Unknown [label="Write"]; - Maybe->Unknown [label="UnknownFunctionCall"]; -} diff --git a/lib/StackAnalysis/UsedReturnValuesOfFunction.dot b/lib/StackAnalysis/UsedReturnValuesOfFunction.dot deleted file mode 100644 index 2c91c72f8..000000000 --- a/lib/StackAnalysis/UsedReturnValuesOfFunction.dot +++ /dev/null @@ -1,21 +0,0 @@ -# -# This file is distributed under the MIT License. See LICENSE.md for details. -# - -digraph UsedReturnValuesOfFunction { - Bottom; - Maybe [peripheries=2]; - YesOrDead; - Unknown; - - # Lattice - Bottom->YesOrDead; - Bottom->Maybe; - YesOrDead->Unknown; - Maybe->Unknown; - - # Transfer functions - Maybe->YesOrDead [label="Write"]; - Maybe->Unknown [label="Read"]; - Maybe->Unknown [label="UnknownFunctionCall"]; -} diff --git a/lib/StackAnalysis/UsedReturnValuesOfFunctionCall.dot b/lib/StackAnalysis/UsedReturnValuesOfFunctionCall.dot deleted file mode 100644 index 040d32b3b..000000000 --- a/lib/StackAnalysis/UsedReturnValuesOfFunctionCall.dot +++ /dev/null @@ -1,19 +0,0 @@ -# -# This file is distributed under the MIT License. See LICENSE.md for details. -# - -digraph UsedReturnValuesOfFunctionCall { - Unknown; - Maybe [peripheries=2]; - Yes; - - # Lattice - Unknown->Maybe; - Maybe->Yes; - - # Transfer functions - Maybe->Yes [label="Read"]; - Maybe->Unknown [label="Write"]; - Maybe->Unknown [label="UnknownFunctionCall"]; - Maybe->Unknown [label="TheCall"]; -} diff --git a/tests/analysis/StackAnalysis/x86_64/recursion.stack-analysis.json b/tests/analysis/StackAnalysis/x86_64/recursion.stack-analysis.json deleted file mode 100644 index fe51488c7..000000000 --- a/tests/analysis/StackAnalysis/x86_64/recursion.stack-analysis.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/tests/analysis/arm/memset.cfg.csv b/tests/analysis/arm/memset.cfg.csv deleted file mode 100644 index 1962a9340..000000000 --- a/tests/analysis/arm/memset.cfg.csv +++ /dev/null @@ -1,37 +0,0 @@ -source,destination -bb._start,bb._start.0xc -bb._start,bb.final -bb._start.0xc,bb.loop -bb.again,bb.again.0x8 -bb.again,bb.final -bb.again.0x18,bb.again.0x28 -bb.again.0x18,bb.final -bb.again.0x28,bb.again.0x34_L0 -bb.again.0x28,bb.again.0x34_L0 -bb.again.0x34_L0,bb.again.0x38_L1 -bb.again.0x34_L0,bb.again.0x38_L1 -bb.again.0x38_L1,bb.again -bb.again.0x38_L1,bb.final -bb.again.0x8,bb.again.0x18 -bb.again.0x8,bb.final -bb.final,bb.final.0x8 -bb.final.0x14,bb.final.0x18 -bb.final.0x18,bb.final.0x1c -bb.final.0x1c,bb.final.0x20 -bb.final.0x20,bb.final.0x24 -bb.final.0x24,bb.final.0x28 -bb.final.0x28,bb.final.0x2c -bb.final.0x8,bb.final.0x14 -bb.final.0x8,bb.final.0x18 -bb.final.0x8,bb.final.0x1c -bb.final.0x8,bb.final.0x20 -bb.final.0x8,bb.final.0x24 -bb.final.0x8,bb.final.0x28 -bb.final.0x8,bb.final.0x2c -bb.loop,bb.loop.0x4_L0 -bb.loop,bb.loop.0x4_L0 -bb.loop.0x10,bb.again -bb.loop.0x4_L0,bb.loop.0x8_L1 -bb.loop.0x4_L0,bb.loop.0x8_L1 -bb.loop.0x8_L1,bb.loop -bb.loop.0x8_L1,bb.loop.0x10 diff --git a/tests/analysis/arm/memset.functions-boundaries.csv b/tests/analysis/arm/memset.functions-boundaries.csv deleted file mode 100644 index 299853942..000000000 --- a/tests/analysis/arm/memset.functions-boundaries.csv +++ /dev/null @@ -1,43 +0,0 @@ -function,basicblock -bb._start,bb._start -bb._start,bb._start.0x8_L0 -bb._start,bb._start.0x8_L0_ft -bb._start,bb._start.0xc -bb._start,bb.again -bb._start,bb.again.0x14_L0 -bb._start,bb.again.0x14_L0_ft -bb._start,bb.again.0x18 -bb._start,bb.again.0x24_L0 -bb._start,bb.again.0x24_L0_ft -bb._start,bb.again.0x28 -bb._start,bb.again.0x34_L0 -bb._start,bb.again.0x34_L0_ft -bb._start,bb.again.0x38_L1 -bb._start,bb.again.0x38_L1_ft -bb._start,bb.again.0x3c_L2 -bb._start,bb.again.0x3c_L2_ft -bb._start,bb.again.0x4_L0 -bb._start,bb.again.0x4_L0_ft -bb._start,bb.again.0x8 -bb._start,bb.final -bb._start,bb.final.0x14 -bb._start,bb.final.0x18 -bb._start,bb.final.0x1c -bb._start,bb.final.0x20 -bb._start,bb.final.0x24 -bb._start,bb.final.0x28 -bb._start,bb.final.0x2c -bb._start,bb.final.0x4_L0 -bb._start,bb.final.0x4_L0_ft -bb._start,bb.final.0x8 -bb._start,bb.final.0x8_epoch_0 -bb._start,bb.final.0x8_epoch_0_address_space_0 -bb._start,bb.final.0x8_epoch_0_address_space_0_type_Code_arm -bb._start,bb.loop -bb._start,bb.loop.0x10 -bb._start,bb.loop.0x4_L0 -bb._start,bb.loop.0x4_L0_ft -bb._start,bb.loop.0x8_L1 -bb._start,bb.loop.0x8_L1_ft -bb._start,bb.loop.0xc_L2 -bb._start,bb.loop.0xc_L2_ft diff --git a/tests/analysis/arm/switch-addls.cfg.csv b/tests/analysis/arm/switch-addls.cfg.csv deleted file mode 100644 index c105815ea..000000000 --- a/tests/analysis/arm/switch-addls.cfg.csv +++ /dev/null @@ -1,13 +0,0 @@ -source,destination -bb._start,bb._start.0x10 -bb._start,bb._start.0x14 -bb._start,bb._start.0x18 -bb._start,bb._start.0x1c -bb._start,bb._start.0x8 -bb._start,bb._start.0xc -bb._start.0x10,bb.end -bb._start.0x14,bb.end -bb._start.0x18,bb.end -bb._start.0x1c,bb.end -bb._start.0x8,bb.end -bb._start.0xc,bb.end diff --git a/tests/analysis/arm/switch-addls.functions-boundaries.csv b/tests/analysis/arm/switch-addls.functions-boundaries.csv deleted file mode 100644 index 18bfc9be9..000000000 --- a/tests/analysis/arm/switch-addls.functions-boundaries.csv +++ /dev/null @@ -1,16 +0,0 @@ -function,basicblock -bb._start,bb._start -bb._start,bb._start.0x10 -bb._start,bb._start.0x14 -bb._start,bb._start.0x18 -bb._start,bb._start.0x1c -bb._start,bb._start.0x4_L0 -bb._start,bb._start.0x4_L0_ft -bb._start,bb._start.0x4_L1 -bb._start,bb._start.0x4_L1_epoch_0 -bb._start,bb._start.0x4_L1_epoch_0_address_space_0 -bb._start,bb._start.0x4_L1_epoch_0_address_space_0_type_Code_arm -bb._start,bb._start.0x4_L1_ft -bb._start,bb._start.0x8 -bb._start,bb._start.0xc -bb._start,bb.end diff --git a/tests/analysis/arm/switch-disjoint-ranges.cfg.csv b/tests/analysis/arm/switch-disjoint-ranges.cfg.csv deleted file mode 100644 index cd285524f..000000000 --- a/tests/analysis/arm/switch-disjoint-ranges.cfg.csv +++ /dev/null @@ -1,51 +0,0 @@ -source,destination -bb._start,bb._start.0xc_L0 -bb._start,bb._start.0xc_L0 -bb._start,bb._start.0xc_L0 -bb._start.0x10_L1,bb._start.0x14_L3 -bb._start.0x10_L1,bb._start.0x14_L3 -bb._start.0x10_L1,bb._start.0x14_L3 -bb._start.0x14_L3,bb.end -bb._start.0x14_L3,bb.indirect_jump -bb._start.0xc_L0,bb._start.0x10_L1 -bb._start.0xc_L0,bb._start.0x10_L1 -bb.indirect_jump,bb.yes1 -bb.indirect_jump,bb.yes1.0x10 -bb.indirect_jump,bb.yes1.0x4 -bb.indirect_jump,bb.yes1.0x8 -bb.indirect_jump,bb.yes1.0xc -bb.indirect_jump,bb.yes2 -bb.indirect_jump,bb.yes2.0x10 -bb.indirect_jump,bb.yes2.0x4 -bb.indirect_jump,bb.yes2.0x8 -bb.indirect_jump,bb.yes2.0xc -bb.no1.0x10_L3,bb.no1.0x14_L4 -bb.no1.0x10_L3,bb.no1.0x14_L4 -bb.no1.0x14_L4,bb.no1.0x18_L5 -bb.no1.0x14_L4,bb.no1.0x18_L5 -bb.no1.0x18_L5,bb.no1.0x1c_L6 -bb.no1.0x18_L5,bb.no1.0x1c_L6 -bb.no1.0x1c_L6,bb.no1.0x20_L7 -bb.no1.0x1c_L6,bb.no1.0x20_L7 -bb.no1.0x20_L7,bb.no1.0x24_L8 -bb.no1.0x20_L7,bb.no1.0x24_L8 -bb.no1.0x24_L8,bb.yes1 -bb.no1.0x24_L8,bb.yes1 -bb.no1.0x4,bb.no1.0x4_L0 -bb.no1.0x4,bb.no1.0x4_L0 -bb.no1.0x4_L0,bb.no1.0x8_L1 -bb.no1.0x4_L0,bb.no1.0x8_L1 -bb.no1.0x8_L1,bb.no1.0xc_L2 -bb.no1.0x8_L1,bb.no1.0xc_L2 -bb.no1.0xc_L2,bb.no1.0x10_L3 -bb.no1.0xc_L2,bb.no1.0x10_L3 -bb.yes1,bb.end -bb.yes1.0x10,bb.end -bb.yes1.0x4,bb.end -bb.yes1.0x8,bb.end -bb.yes1.0xc,bb.end -bb.yes2,bb.end -bb.yes2.0x10,bb.end -bb.yes2.0x4,bb.end -bb.yes2.0x8,bb.end -bb.yes2.0xc,bb.end diff --git a/tests/analysis/arm/switch-disjoint-ranges.functions-boundaries.csv b/tests/analysis/arm/switch-disjoint-ranges.functions-boundaries.csv deleted file mode 100644 index 71c8ad15c..000000000 --- a/tests/analysis/arm/switch-disjoint-ranges.functions-boundaries.csv +++ /dev/null @@ -1,30 +0,0 @@ -function,basicblock -bb._start,bb._start -bb._start,bb._start.0x10_L1 -bb._start,bb._start.0x10_L1_ft -bb._start,bb._start.0x10_L2 -bb._start,bb._start.0x10_L2_ft -bb._start,bb._start.0x14_L3 -bb._start,bb._start.0x14_L3_ft -bb._start,bb._start.0x14_L3_ft2 -bb._start,bb._start.0x18_L4 -bb._start,bb._start.0x18_L4_ft -bb._start,bb._start.0x18_L4_ft3 -bb._start,bb._start.0xc_L0 -bb._start,bb._start.0xc_L0_ft -bb._start,bb._start.0xc_L0_ft1 -bb._start,bb.end -bb._start,bb.indirect_jump -bb._start,bb.indirect_jump_epoch_0 -bb._start,bb.indirect_jump_epoch_0_address_space_0 -bb._start,bb.indirect_jump_epoch_0_address_space_0_type_Code_arm -bb._start,bb.yes1 -bb._start,bb.yes1.0x10 -bb._start,bb.yes1.0x4 -bb._start,bb.yes1.0x8 -bb._start,bb.yes1.0xc -bb._start,bb.yes2 -bb._start,bb.yes2.0x10 -bb._start,bb.yes2.0x4 -bb._start,bb.yes2.0x8 -bb._start,bb.yes2.0xc diff --git a/tests/analysis/arm/switch-ldrls.cfg.csv b/tests/analysis/arm/switch-ldrls.cfg.csv deleted file mode 100644 index 8eed0007c..000000000 --- a/tests/analysis/arm/switch-ldrls.cfg.csv +++ /dev/null @@ -1,14 +0,0 @@ -source,destination -bb._start,bb._start.0x8 -bb._start,bb.end -bb._start,bb.one -bb._start,bb.three -bb._start,bb.two -bb._start.0x8,bb.end -bb.no,bb.no_L0 -bb.no,bb.no_L0 -bb.no.0x4_L1,bb.no.0x8_L2 -bb.no.0x4_L1,bb.no.0x8_L2 -bb.no.0x8_L2,bb.one -bb.no_L0,bb.no.0x4_L1 -bb.no_L0,bb.no.0x4_L1 diff --git a/tests/analysis/arm/switch-ldrls.functions-boundaries.csv b/tests/analysis/arm/switch-ldrls.functions-boundaries.csv deleted file mode 100644 index 5680cb108..000000000 --- a/tests/analysis/arm/switch-ldrls.functions-boundaries.csv +++ /dev/null @@ -1,14 +0,0 @@ -function,basicblock -bb._start,bb._start -bb._start,bb._start.0x4_L0 -bb._start,bb._start.0x4_L0_ft -bb._start,bb._start.0x4_L1 -bb._start,bb._start.0x4_L1_epoch_0 -bb._start,bb._start.0x4_L1_epoch_0_address_space_0 -bb._start,bb._start.0x4_L1_epoch_0_address_space_0_type_Code_arm -bb._start,bb._start.0x4_L1_ft -bb._start,bb._start.0x8 -bb._start,bb.end -bb._start,bb.one -bb._start,bb.three -bb._start,bb.two diff --git a/tests/analysis/mips/jump-table-base-before-function-call.cfg.csv b/tests/analysis/mips/jump-table-base-before-function-call.cfg.csv deleted file mode 100644 index 1a0c56055..000000000 --- a/tests/analysis/mips/jump-table-base-before-function-call.cfg.csv +++ /dev/null @@ -1,18 +0,0 @@ -source,destination -bb.__start,bb.__start.0xc -bb.__start,bb.end -bb.__start.0x28,bb.__start.0x30 -bb.__start.0x30,bb.__start.0x40 -bb.__start.0x30,bb.nullptr -bb.__start.0x40,bb.one -bb.__start.0x40,bb.three -bb.__start.0x40,bb.two -bb.__start.0xc,bb.function -bb.function,bb.__start.0x28 -bb.jumptable,bb.jumptable.0x4 -bb.jumptable.0x4,bb.jumptable.0x8 -bb.jumptable.0x8,bb.__start -bb.nullptr,bb.__start.0x28 -bb.one,bb.__start.0x28 -bb.three,bb.__start.0x28 -bb.two,bb.__start.0x28 diff --git a/tests/analysis/mips/switch-jump-table.cfg.csv b/tests/analysis/mips/switch-jump-table.cfg.csv deleted file mode 100644 index 8893edbc9..000000000 --- a/tests/analysis/mips/switch-jump-table.cfg.csv +++ /dev/null @@ -1,8 +0,0 @@ -source,destination -bb.__start,bb.__start.0xc -bb.__start,bb.end -bb.__start.0x2c,bb.one -bb.__start.0x2c,bb.three -bb.__start.0x2c,bb.two -bb.__start.0xc,bb.__start.0x2c -bb.__start.0xc,bb.nullptr diff --git a/tests/analysis/mips/switch-jump-table.functions-boundaries.csv b/tests/analysis/mips/switch-jump-table.functions-boundaries.csv deleted file mode 100644 index 6071b3eb5..000000000 --- a/tests/analysis/mips/switch-jump-table.functions-boundaries.csv +++ /dev/null @@ -1,18 +0,0 @@ -function,basicblock -bb.__start,bb.__start -bb.__start,bb.__start.0x18_L0 -bb.__start,bb.__start.0x18_L0_ft -bb.__start,bb.__start.0x24_L1 -bb.__start,bb.__start.0x24_L1_ft -bb.__start,bb.__start.0x2c -bb.__start,bb.__start.0x2c_epoch_0 -bb.__start,bb.__start.0x2c_epoch_0_address_space_0 -bb.__start,bb.__start.0x2c_epoch_0_address_space_0_type_Code_mips -bb.__start,bb.__start.0x4_L0 -bb.__start,bb.__start.0x4_L0_ft -bb.__start,bb.__start.0xc -bb.__start,bb.end -bb.__start,bb.nullptr -bb.__start,bb.one -bb.__start,bb.three -bb.__start,bb.two diff --git a/tests/analysis/x86_64/rda-in-memory.cfg.csv b/tests/analysis/x86_64/rda-in-memory.cfg.csv deleted file mode 100644 index 9d5635e42..000000000 --- a/tests/analysis/x86_64/rda-in-memory.cfg.csv +++ /dev/null @@ -1,7 +0,0 @@ -source,destination -bb._start,bb._start.0x6 -bb._start,bb.end -bb._start.0x6,bb.end -bb._start.0x6,bb.one -bb._start.0x6,bb.three -bb._start.0x6,bb.two diff --git a/tests/analysis/x86_64/switch-jump-table-32-bit-comparison.cfg.csv b/tests/analysis/x86_64/switch-jump-table-32-bit-comparison.cfg.csv deleted file mode 100644 index 3c01ee0e2..000000000 --- a/tests/analysis/x86_64/switch-jump-table-32-bit-comparison.cfg.csv +++ /dev/null @@ -1,9 +0,0 @@ -source,destination -bb._start,bb._start.0x26 -bb._start,bb._start.0x8 -bb._start.0x26,bb._start.0x28 -bb._start.0x8,bb._start.0x14 -bb._start.0x8,bb._start.0x1a -bb._start.0x8,bb._start.0x20 -bb._start.0x8,bb._start.0x26 -bb._start.0x8,bb._start.0x28 diff --git a/tests/analysis/x86_64/switch-jump-table.cfg.csv b/tests/analysis/x86_64/switch-jump-table.cfg.csv deleted file mode 100644 index 9d5635e42..000000000 --- a/tests/analysis/x86_64/switch-jump-table.cfg.csv +++ /dev/null @@ -1,7 +0,0 @@ -source,destination -bb._start,bb._start.0x6 -bb._start,bb.end -bb._start.0x6,bb.end -bb._start.0x6,bb.one -bb._start.0x6,bb.three -bb._start.0x6,bb.two diff --git a/tests/analysis/x86_64/switch-jump-table.functions-boundaries.csv b/tests/analysis/x86_64/switch-jump-table.functions-boundaries.csv deleted file mode 100644 index 0d05e57cd..000000000 --- a/tests/analysis/x86_64/switch-jump-table.functions-boundaries.csv +++ /dev/null @@ -1,12 +0,0 @@ -function,basicblock -bb._start,bb._start -bb._start,bb._start.0x4_L0 -bb._start,bb._start.0x4_L0_ft -bb._start,bb._start.0x6 -bb._start,bb._start.0x6_epoch_0 -bb._start,bb._start.0x6_epoch_0_address_space_0 -bb._start,bb._start.0x6_epoch_0_address_space_0_type_Code_x86_64 -bb._start,bb.end -bb._start,bb.one -bb._start,bb.three -bb._start,bb.two diff --git a/tests/analysis/x86_64/try-catch-ehframe.cfg.csv b/tests/analysis/x86_64/try-catch-ehframe.cfg.csv deleted file mode 100644 index d7a7d7dc6..000000000 --- a/tests/analysis/x86_64/try-catch-ehframe.cfg.csv +++ /dev/null @@ -1,10 +0,0 @@ -source,destination -bb._start,bb.__gxx_personality_v0 -bb._start.0x13,bb._start.0x3e -bb._start.0x17,bb._start.0x1d -bb._start.0x17,bb._start.0x25 -bb._start.0x1d,bb.__gxx_personality_v0 -bb._start.0x25,bb.__gxx_personality_v0 -bb._start.0x2d,bb.__gxx_personality_v0 -bb._start.0x3c,bb._start.0x13 -bb._start.0xe,bb._start.0x13 diff --git a/tests/analysis/x86_64/try-catch-ehframe.functions-boundaries.csv b/tests/analysis/x86_64/try-catch-ehframe.functions-boundaries.csv deleted file mode 100644 index 9746521a8..000000000 --- a/tests/analysis/x86_64/try-catch-ehframe.functions-boundaries.csv +++ /dev/null @@ -1,6 +0,0 @@ -function,basicblock -bb.__gxx_personality_v0,bb.__gxx_personality_v0 -bb._start,bb._start -bb._start,bb._start.0x13 -bb._start,bb._start.0x3e -bb._start,bb._start.0xe diff --git a/tests/unit/StackAnalysis.cpp b/tests/unit/StackAnalysis.cpp deleted file mode 100644 index 4a7ea37d7..000000000 --- a/tests/unit/StackAnalysis.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/// \file StackAnalysis.cpp -/// \brief Tests for StackAnalysis data structures - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#define BOOST_TEST_MODULE StackAnalysis -bool init_unit_test(); -#include "boost/test/unit_test.hpp" - -#include "revng/UnitTestHelpers/UnitTestHelpers.h" - -#include "Intraprocedural.h" - -using namespace StackAnalysis; - -BOOST_TEST_DONT_PRINT_LOG_VALUE(ASID) -BOOST_TEST_DONT_PRINT_LOG_VALUE(ASSlot) -BOOST_TEST_DONT_PRINT_LOG_VALUE(std::vector) - -const ASID SP0 = ASID::stackID(); -const ASID GLB = ASID::globalID(); -const ASID CPU = ASID::cpuID(); -const ASID Invalid = ASID::invalidID(); - -using ASVector = std::vector; - -BOOST_AUTO_TEST_CASE(TestAddressSpaceID) { - // Test ordering - BOOST_TEST(SP0.lowerThanOrEqual(SP0)); - BOOST_TEST(not SP0.lowerThanOrEqual(GLB)); -} - -BOOST_AUTO_TEST_CASE(TestASSlot) { - // Test comparisons - ASSlot SP0Slot = ASSlot::create(SP0, 0); - ASSlot CPUSlot = ASSlot::create(CPU, 0); - BOOST_TEST(SP0Slot == SP0Slot); - BOOST_TEST(SP0Slot != CPUSlot); - BOOST_TEST(not SP0Slot.lowerThanOrEqual(CPUSlot)); - - // Test addition and masking - SP0Slot.add(-5); - BOOST_TEST(SP0Slot.offset() == -5); - SP0Slot.add(+10); - BOOST_TEST(SP0Slot.offset() == +5); - SP0Slot.mask(1); - BOOST_TEST(SP0Slot.offset() == +1); - - // Test usage in a map - std::map Map; - Map[SP0Slot] = 0; - BOOST_TEST(Map.count(SP0Slot) != 0U); -} diff --git a/tests/unit/UnitTests.cmake b/tests/unit/UnitTests.cmake index bacb2b967..65721d102 100644 --- a/tests/unit/UnitTests.cmake +++ b/tests/unit/UnitTests.cmake @@ -25,26 +25,6 @@ target_link_libraries(test_lazysmallbitvector add_test(NAME test_lazysmallbitvector COMMAND ./bin/test_lazysmallbitvector) set_tests_properties(test_lazysmallbitvector PROPERTIES LABELS "unit") -# -# test_stackanalysis -# - -revng_add_private_executable(test_stackanalysis "${SRC}/StackAnalysis.cpp") -target_compile_definitions(test_stackanalysis - PRIVATE "BOOST_TEST_DYN_LINK=1") -target_include_directories(test_stackanalysis - PRIVATE "${CMAKE_SOURCE_DIR}" - "${CMAKE_SOURCE_DIR}/lib/StackAnalysis" - "${CMAKE_BINARY_DIR}/lib/StackAnalysis") -target_link_libraries(test_stackanalysis - revngStackAnalysis - revngSupport - revngUnitTestHelpers - Boost::unit_test_framework - ${LLVM_LIBRARIES}) -add_test(NAME test_stackanalysis COMMAND ./bin/test_stackanalysis) -set_tests_properties(test_stackanalysis PROPERTIES LABELS "unit") - # # test_classsentinel #