Files
revng-revng/lib/StackAnalysis/FunctionABI.h
Alessandro Di Federico 28cb935d39 ZipMapIterator: handle dishomogeneous containers
ZipMapIterator can now handle containers of different type, as long as
their keys are comparable.
2021-01-27 19:46:52 +01:00

614 lines
17 KiB
C++

#pragma once
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include <sstream>
#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<typename K, typename V, size_t N = 40>
class DefaultMap {
public:
// TODO: the size of the SmallMap needs to be fine tuned
using Container = SmallMap<K, V, N>;
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<typename E>
class Element;
}
class ABIFunction;
struct CombineHelper {
/// \brief Combine with URAOF
template<bool FunctionCall>
static void combine(RegisterArgument<FunctionCall> &This,
UsedArgumentsOfFunction::Values V) {
revng_assert(!FunctionCall);
if (V == UsedArgumentsOfFunction::Yes) {
switch (This.Value) {
case RegisterArgument<FunctionCall>::NoOrDead:
This.Value = RegisterArgument<FunctionCall>::Contradiction;
break;
case RegisterArgument<FunctionCall>::Maybe:
This.Value = RegisterArgument<FunctionCall>::Yes;
break;
case RegisterArgument<FunctionCall>::No:
// No comes from ECS and wins over everything
break;
case RegisterArgument<FunctionCall>::Dead:
case RegisterArgument<FunctionCall>::Yes:
case RegisterArgument<FunctionCall>::Contradiction:
revng_abort();
}
}
}
/// \brief Combine with DRAOF
template<bool FunctionCall>
static void combine(RegisterArgument<FunctionCall> &This,
DeadRegisterArgumentsOfFunction::Values V) {
revng_assert(not FunctionCall);
if (V == DeadRegisterArgumentsOfFunction::NoOrDead) {
switch (This.Value) {
case RegisterArgument<FunctionCall>::Maybe:
This.Value = RegisterArgument<FunctionCall>::NoOrDead;
break;
case RegisterArgument<FunctionCall>::Yes:
This.Value = RegisterArgument<FunctionCall>::Contradiction;
break;
case RegisterArgument<FunctionCall>::No:
// No comes from ECS and wins over everything
break;
case RegisterArgument<FunctionCall>::NoOrDead:
case RegisterArgument<FunctionCall>::Dead:
case RegisterArgument<FunctionCall>::Contradiction:
revng_abort();
}
}
}
/// \brief Combine with RAOFC
template<bool FunctionCall>
static void combine(RegisterArgument<FunctionCall> &This,
RegisterArgumentsOfFunctionCall::Values V) {
revng_assert(FunctionCall);
if (V == RegisterArgumentsOfFunctionCall::Yes) {
switch (This.Value) {
case RegisterArgument<FunctionCall>::NoOrDead:
This.Value = RegisterArgument<FunctionCall>::Dead;
break;
case RegisterArgument<FunctionCall>::Maybe:
This.Value = RegisterArgument<FunctionCall>::Yes;
break;
case RegisterArgument<FunctionCall>::Yes:
case RegisterArgument<FunctionCall>::Dead:
case RegisterArgument<FunctionCall>::Contradiction:
break;
case RegisterArgument<FunctionCall>::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<typename V, typename T>
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<typename T>
CallSiteRegisterState &assign(const T &Other) {
T::H::assign(*this, Other.Analyses);
return *this;
}
template<typename T>
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<typename T>
void dump(T &Output, const char *Prefix) const {
dumpAnalysis(Output, Prefix, RAOFC);
dumpAnalysis(Output, Prefix, URVOFC);
dumpAnalysis(Output, Prefix, DRVOFC);
}
private:
template<typename A, typename B, bool C, bool D, size_t E>
friend struct AnalysesWrapperHelpers;
template<typename T>
T &getByType();
template<typename T>
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<typename T>
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<typename T>
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<typename A, typename B, bool C, bool D, size_t E>
friend struct AnalysesWrapperHelpers;
template<typename T>
T &getByType();
template<typename T>
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<typename Enabled>
friend class ABIAnalysis::Element;
private:
struct CallsAnalyses {
DefaultMap<int32_t, CallSiteRegisterState, 20> Registers;
};
private:
DefaultMap<int32_t, RegisterState, 20> RegisterAnalyses;
DefaultMap<FunctionCall, CallsAnalyses, 5> 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<typename E>
void combine(const ABIAnalysis::Element<E> &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<ASSlot> &SlotsPool) const {
for (auto &P : RegisterAnalyses)
SlotsPool.insert(ASSlot::create(ASID::cpuID(), P.first));
}
std::pair<std::set<int32_t>, std::set<int32_t>> collectYesRegisters() const {
std::set<int32_t> Arguments;
std::set<int32_t> 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<typename T>
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