/// \file functionabi.cpp /// \brief Implementation of the ABI analysis // // This file is distributed under the MIT License. See LICENSE.md for details. // // Local includes #include "abiir.h" #include "functionabi.h" #include "monotoneframework.h" using std::conditional; using std::tuple; using std::tuple_element; using std::tuple_size; using llvm::Module; Logger<> SaABI("sa-abi"); namespace StackAnalysis { using ABIIRBB = ABIIRBasicBlock; static ASID CPU = ASID::cpuID(); /// \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; for (auto &P : This) { P.second.template cmp(Other.getOrDefault(P.first)); ROA((P.second.template cmp(Other.getOrDefault(P.first))), { SaDiffLog << P.first << DoLog; }); } for (auto &P : Other) { ROA((This.getOrDefault(P.first).template cmp(P.second)), { SaDiffLog << P.first << DoLog; }); } return Result; } template unsigned cmpWithModule(const DefaultMap &This, const DefaultMap &Other, ASID ID, const Module *M) { LoggerIndent<> Y(SaDiffLog); unsigned Result = 0; 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 DefaultMap, N2> &This, const DefaultMap, N2> &Other, ASID ID, const Module *M) { LoggerIndent<> Y(SaDiffLog); unsigned Result = 0; 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) { combine(This.Default, Other.Default); 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); } template inline void dump(const Module *M, T &Output, const DefaultMap &D, ASID ID) { for (auto &P : D) { ASSlot::create(ID, P.first).dump(M, Output); Output << ":\n"; P.second.dump(Output); Output << "\n"; } } 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 DefaultMap, N2> &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) { returnFromCall(This.Default, Other.Default); 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); } 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 greaterThan(const Inhibitor &Other) const { return not lowerThanOrEqual(Other); } 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 (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 DefaultMap, 5> 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 { LoggerIndent<> Y(SaDiffLog); unsigned Result = 0; auto registerCmp = MapHelpers::cmpWithModule; ROA((registerCmp(RegisterAnalyses, Other.RegisterAnalyses, CPU, M)), { SaDiffLog << "RegisterAnalyses" << DoLog; }); auto X = MapHelpers::nestedCmpWithModule; ROA((X(FunctionCallRegisterAnalyses, Other.FunctionCallRegisterAnalyses, CPU, M)), { SaDiffLog << "RegisterAnalyses" << DoLog; }); return Result; } bool greaterThan(const Element &Other) const { return not lowerThanOrEqual(Other); } 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 debug_function { dump(M, dbg); } template void dump(const Module *M, T &Output) const { std::stringstream Stream; dumpInternal(M, Stream); Output << Stream.str(); } private: void dumpInternal(const Module *M, std::stringstream &Output) const { MapHelpers::dump(M, Output, RegisterAnalyses, CPU); MapHelpers::dump(M, Output, FunctionCallRegisterAnalyses, CPU, " "); } }; /// \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