Files
revng-revng/stackanalysis.cpp
T
Alessandro Di Federico a5af28621b Introducing the stack analysis
The stack analysis is the foundation to obtain accurate information
about the body of a function, which registers are callee-saved,
arguments, return values and so on.

It is implemented as a pass to run in revamb-dump.

This commit also introduces analysis tests specific to what we aim to
obtain from the analysis and also some basic unit tests for data
structures related to the stack analysis.
2017-08-12 16:56:23 +02:00

2151 lines
66 KiB
C++

/// \file stackanalysis.cpp
/// \brief
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
// TODO: reduce copy of Element (check arguments and return values)
// Standard includes
#include <array>
#include <map>
#include <queue>
#include <sstream>
#include <vector>
// Boost includes
#include <boost/algorithm/cxx11/any_of.hpp>
// Local includes
#include "datastructures.h"
#include "debug.h"
#include "ir-helpers.h"
#include "jumptargetmanager.h"
#include "revamb.h"
#include "stackanalysis.h"
#include "stackanalysis_impl.h"
// We don't put using namespace llvm since it would create conflicts with the
// local definition of Value
using llvm::AllocaInst;
using llvm::BlockAddress;
using llvm::CallInst;
using llvm::cast;
using llvm::Constant;
using llvm::ConstantInt;
using llvm::DataLayout;
using llvm::dyn_cast;
using llvm::dyn_cast_or_null;
using llvm::Function;
using llvm::isa;
using llvm::LoadInst;
using llvm::Metadata;
using llvm::MDNode;
using llvm::MDString;
using llvm::MDTuple;
using llvm::RegisterPass;
using llvm::SmallBitVector;
using llvm::SmallVector;
using llvm::StoreInst;
using llvm::StringRef;
using llvm::TerminatorInst;
using llvm::UnreachableInst;
using llvm::User;
using namespace StackAnalysis;
/// \brief Given an array of GlobalVariable/AllocaInst, returns a unique index
/// for each one of them
///
/// Get an index for each GlobalVariabe defined in \p M and each AllocaInst in
/// the root function.
///
/// \tparam N the number of elements to search
///
/// \return a std::array of integers representing the index of the corresponding
/// input GlobalVariable/AllocaInst, or -1 if the request object
/// couldn't be found
template<unsigned N>
static inline std::array<int32_t, N>
getCPUIndex(const Module *M, std::array<const User *, N> Search) {
std::array<int32_t, N> Result;
// Initialize results to -1
for (int32_t &Index : Result)
Index = -1;
// Go through global variables first
int32_t I = 0;
for (const GlobalVariable &GV : M->globals()) {
int32_t J = 0;
for (const User *U : Search) {
if (U == &GV) {
assert(Result[J] == -1);
Result[J] = I;
break;
}
J++;
}
I++;
}
// Look for AllocaInst at the beginning of the root function
const BasicBlock *Entry = &*M->getFunction("root")->begin();
auto It = Entry->begin();
while (It != Entry->end() && isa<AllocaInst>(&*It)) {
int32_t J = 0;
for (const User *U : Search) {
if (U == &*It) {
assert(Result[J] == -1);
Result[J] = I;
break;
}
J++;
}
I++;
It++;
}
return Result;
}
/// \brief CRTP base class for an element of the lattice
///
/// \note This class is more for reference. It's unused.
///
/// \tparam D the derived class.
template<typename D>
class ElementBase {
public:
/// \brief The partial ordering relation
bool lowerThanOrEqual(const ElementBase &RHS) const {
const D &This = *static_cast<const D *>(this);
const D &Other = static_cast<const D &>(RHS);
return This.lowerThanOrEqual(Other);
}
/// \brief The opposite of the partial ordering operation
bool greaterThan(const ElementBase &RHS) const {
return !this->lowerThanOrEqual(RHS);
}
/// \brief The combination operator
// TODO: assert monotonicity
ElementBase &combine(const ElementBase &RHS) {
return static_cast<const D *>(this)->combine(static_cast<const D &>(RHS));
}
};
/// \brief CRTP base class for implementing a monotone framework
///
/// This class provides the base structure to implement an analysis based on a
/// monotone framework. It also provides an implementation of the MFP solution.
///
/// \tparam Label the type identifying a "label" in the monotone framework,
/// typically an instruction or a basic block.
/// \tparam LatticeElement the type representing an element of the lattice.
/// \tparam Interrupt the type describing why the analysis has been interrupted.
/// \tparam D the derived class.
template<typename Label,
typename LatticeElement,
typename Interrupt,
typename D>
class MonotoneFramework {
public:
using LabelRange = std::vector<Label>;
/// \brief The transfer function
///
/// Starting from the initial state at \p BB provides a new lattice element or
/// a reason why the analysis should be interrupted.
Interrupt transfer(Label L) {
return static_cast<D *>(this)->transfer(L);
}
/// \brief Return a list of all the extremal labels
///
/// An extremal node is typically the entry or the exit nodes of the function,
/// depending on whether the analysis being implemented is a forward or
/// backward analysis.
LabelRange extremalLabels() const {
return static_cast<const D *>(this)->extremalLabels();
}
/// \brief Return the element of the lattice associated with an extremal label
LatticeElement extremalValue() {
return static_cast<D *>(this)->extremalValue();
}
/// \brief Initialize/reset the analysis
///
/// Call this method before invoking run or if you want to reset the state of
/// the analysis.
void initialize() {
FirstFinalState = true;
State.clear();
WorkList.clear();
Extremals.clear();
for (Label ExtremalLabel : extremalLabels()) {
WorkList.insert(ExtremalLabel);
State[ExtremalLabel] = extremalValue();
}
}
/// \brief Resolve the data flow analysis problem using the MFP solution
Interrupt run() {
while (!WorkList.empty()) {
Label ToAnalyze = WorkList.head();
ToVisit.erase(ToAnalyze);
// Run the transfer function
Interrupt Result = transfer(ToAnalyze);
// Check if we should continue or if we should yield control to the
// caller, i.e., the interprocedural part of the analysis, if present.
if (Result.requiresInterproceduralHandling())
return Result;
// OK, we can handle this result by ourselves: get the result and pop an
// element from the work list
const LatticeElement &NewLattice = Result.getResult();
WorkList.pop();
// Are we in a final state?
if (Result.isFinalState()) {
// If so, accumulate the result in FinalResult
if (FirstFinalState) {
FinalResult = NewLattice;
FirstFinalState = false;
} else {
FinalResult.combine(NewLattice);
}
DBG("sa", {
dbg << "FinalResult:\n";
FinalResult.dump(getModule(ToAnalyze));
dbg << "\n";
});
}
// If it has successors, check if we have to re-enqueue them
if (Result.hasSuccessors()) {
for (Label Successor : Result) {
auto It = State.find(Successor);
if (It == State.end()) {
// We have never seen this Label, register it in the analysis state
State[Successor] = NewLattice;
WorkList.insert(Successor);
} else if (NewLattice.greaterThan(It->second)) {
// We have already seen this Label but the result of the transfer
// function is larger than its previous initial state: re-enqueue it
State[Successor].combine(NewLattice);
WorkList.insert(Successor);
}
}
}
}
// The work list is empty, return to the caller the final result
assert(ToVisit.empty());
if (FirstFinalState) {
return Interrupt::createNoReturnFunction();
} else {
return Interrupt::createSummary(FinalResult);
}
}
/// \brief Registers \p L to be visited before the end of the analysis
///
/// If \p L has already been visited at least once before, it's simply
/// enqueued in the WorkList, otherwise is registered to be visited at least
/// once before the end of the analysis.
///
/// This function is required when you want to visit a basic block only if
/// it's part of the current function, or fail otherwise.
void registerToVisit(Label L) {
if (State.count(L) == 0)
ToVisit.insert(L);
else
WorkList.insert(L);
}
public:
bool FirstFinalState;
LatticeElement FinalResult;
UniquedQueue<Label> WorkList;
std::set<Label> Extremals;
std::map<Label, LatticeElement> State;
std::set<Label> ToVisit;
};
/// \brief Class that allows querying mutiple maps at the same time through a
/// "fallback" mechanism
///
/// Given an index, it will be searched in all the registered maps, and the
/// first matching result will be returned.
///
/// \tparam M type of the map container
template<typename M>
class LayeredMap {
public:
using iterator = typename M::const_iterator;
using key_type = typename M::key_type;
using mapped_type = typename M::mapped_type;
public:
LayeredMap(mapped_type &Default) : Default(Default) { }
/// \brief Register a new map
void add(const M &Map) { Maps.push_back(&Map); }
const mapped_type &operator[](key_type &Key) const {
auto It = find(Key);
if (It != end())
return It->second;
return Default;
}
iterator find(const key_type &Key) const {
for (const M *Map : Maps) {
iterator It = Map->find(Key);
if (It != Map->end())
return It;
}
return end();
}
iterator end() const {
return Maps[0]->end();
}
bool contains(const key_type &Key) const {
return find(Key) != end();
}
private:
SmallVector<const M *, 2> Maps;
mapped_type &Default;
};
/// \brief Class to keep track of the Value associated to each instruction in a
/// basic block
class BasicBlockState {
public:
using ContentMap = std::map<Instruction *, Value>;
public:
BasicBlockState(ContentMap &VariableContent,
const DataLayout &DL,
ASID Last) :
Default(),
VariableContent(VariableContent),
Content(Default),
DL(DL),
Last(Last) {
Content.add(InstructionContent);
Content.add(VariableContent);
}
/// \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 {
if (auto *CSV = dyn_cast<AllocaInst>(V)) {
int32_t Index;
Index = getCPUIndex<1>(getModule(CSV),
{ static_cast<const User *>(CSV) })[0];
assert(Index != -1);
return Value(ASSlot(ASID::cpuID(), Index));
} else if (auto *CSV = dyn_cast<GlobalVariable>(V)) {
int32_t Index;
Index = getCPUIndex<1>(CSV->getParent(),
{ static_cast<const User *>(CSV) })[0];
assert(Index != -1);
return Value(ASSlot(ASID::cpuID(), Index));
} else if (auto *C = dyn_cast<Constant>(V)) {
return Value(ASSlot(ASID::globalID(), getZExtValue(C, DL)));
}
Instruction *I = cast<Instruction>(V);
assert(Content.contains(I));
return Content[I];
}
/// \brief Register the value of instruction \p I
void set(Instruction *I, Value V) {
assert(I != nullptr);
DBG("sa-verbose", {
dbg << "Set " << getName(I) << " to ";
V.dump(getModule(I));
dbg << "\n";
});
assert(InstructionContent.count(I) == 0);
InstructionContent[I] = V;
}
/// \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) {
assert(I->getNumOperands() > 0);
Value Result = get(I->getOperand(0));
DBG("sa-verbose", {
dbg << "Merging operands: ";
Result.dump(getModule(I));
});
for (llvm::Value *Operand : skip(1, I->operands())) {
Value OperandValue = get(Operand);
DBG("sa-verbose", {
dbg << ", ";
OperandValue.dump(getModule(I));
});
Result.combine(OperandValue);
}
DBG("sa-verbose", dbg << "\n");
set(I, Result);
}
std::set<BasicBlock *> save() {
std::set<BasicBlock *> Result;
BasicBlock *BB = nullptr;
for (auto &P : InstructionContent) {
Instruction *I = P.first;
Value &NewValue = P.second;
assert(BB == nullptr || BB == I->getParent());
BB = I->getParent();
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 (NewValue.greaterThan(OldValue, Last)) {
OldValue = NewValue;
Changed = true;
}
}
if (Changed) {
for (User *U : I->users()) {
if (auto *UserI = dyn_cast<Instruction>(U)) {
BasicBlock *UserBB = UserI->getParent();
if (UserBB != BB)
Result.insert(UserBB);
}
}
}
}
}
return Result;
}
private:
Value Default;
ContentMap InstructionContent;
ContentMap &VariableContent;
LayeredMap<ContentMap> Content;
const DataLayout &DL;
ASID Last;
};
/// \brief Compare two bit vectors
static inline bool
lowerThanOrEqual(const SmallBitVector &LHS, const SmallBitVector &RHS) {
assert(LHS.size() == RHS.size());
auto T = LHS;
T &= RHS;
return T == LHS;
}
static inline bool
greaterThan(const SmallBitVector &LHS, const SmallBitVector &RHS) {
return !(lowerThanOrEqual(LHS, RHS));
}
/// \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: for each a function, a set of
/// <Context, Result> pairs are stored.
/// * the set of "fake" functions.
/// * the association between each function and its return register.
class Cache {
private:
struct FunctionSummary {
SmallVector<Element, 1> MatchingContexts;
LoadStoreLog Summary;
bool Temporary;
};
struct FunctionInfo {
ASIndexer Indexer;
std::vector<FunctionSummary> Summaries;
};
public:
using ReturnAddressesMap = std::map<BasicBlock *, GlobalVariable *>;
public:
Cache() : Default(nullptr) { }
ASIndexer &getIndexer(BasicBlock *Entry) {
return Results[Entry].Indexer;
}
const ASIndexer &getIndexer(BasicBlock *Entry) const {
auto It = Results.find(Entry);
assert(It != Results.end());
return It->second.Indexer;
}
bool isFakeFunction(BasicBlock *Function) const {
return FakeFunctions.count(Function) != 0;
}
void markAsFake(BasicBlock *Function) { FakeFunctions.insert(Function); }
bool isNoReturnFunction(BasicBlock *Function) const {
return NoReturnFunctions.count(Function) != 0;
}
void markAsNoReturn(BasicBlock *Function) {
NoReturnFunctions.insert(Function);
}
/// \brief Query the cache for the result of the analysis for a specific
/// function with a specific context
///
/// \return the matching result, if available.
Optional<LoadStoreLog>
get(BasicBlock *Function, Element Query) const {
auto It = Results.find(Function);
if (It != Results.end())
for (const FunctionSummary &Summary : It->second.Summaries)
if (matches(&getIndexer(Function), Summary, Query))
return Summary.Summary;
return Optional<LoadStoreLog>();
}
/// \brief Insert in the cache an entry for the function \p Function
///
/// \return true if this function-context was already present with a different
/// result.
// TODO: Result for reference
bool update(BasicBlock *Function,
SmallVector<Element, 1> Contexts,
LoadStoreLog Result,
bool Temporary=false) {
ASIndexer *Indexer = &getIndexer(Function);
DBG("sa", {
dbg << "Cache.update(" << getName(Function) << "), with contexts\n";
for (Element &Context : Contexts) {
Context.dump(getModule(Function));
dbg << "\n";
}
dbg << "with value\n";
Result.dump(getModule(Function));
dbg << "\n";
});
auto It = Results.find(Function);
if (It == Results.end()) {
for (Element &Context : Contexts)
Context.prune(&Result, Indexer);
Results[Function].Summaries.push_back({ Contexts, Result, Temporary });
return false;
} else {
bool Changed = false;
auto &Summaries = It->second.Summaries;
// For each input context
for (auto ContextIt = Contexts.begin();
ContextIt != Contexts.end();
/**/) {
Element &Context = *ContextIt;
bool Remove = false;
// We have three situations:
//
// 1. the context doesn't match any context in an existing summary: we
// will insert it in a new summary
// 2. the context matches a context in an existing summary and the
// associated summary is smaller than the current one: we leave the
// context where it is and do not add it to the new summary
// 3. the context matches a conext in an existing summary and the
// associated summary is larger than the current one: we drop the
// context from the existing summary and we will insert it in a new
// summary
// For each summary
for (auto SummaryIt = Summaries.begin();
SummaryIt != Summaries.end();
/**/) {
FunctionSummary &Summary = *SummaryIt;
// Prepare the comparison among the current context and the contexts
// of this summary
Element Pruned = Context;
Pruned.prune(&Summary.Summary, Indexer);
auto Predicate = [&Pruned] (Element &Candidate) {
return Pruned.equal(Candidate);
};
// Is the summary temporary or do we have an increase in precision?
if (Summary.Temporary
|| (Result.Store.lowerThanOrEqual(Summary.Summary.Store)
&& !Summary.Summary.Store.lowerThanOrEqual(Result.Store))) {
unsigned OldSize = Summary.MatchingContexts.size();
// Remove all the matching contexts for this summary (the new result
// is more precise)
erase_if(Summary.MatchingContexts, Predicate);
unsigned NewSize = Summary.MatchingContexts.size();
// If we dropped at least one element, inform the the caller that we
// changed the summary associated with a previously known context
if (OldSize != NewSize)
Changed = true;
// If there are not matching contexts left, drop this summary
// altogether
if (NewSize == 0) {
SummaryIt = Summaries.erase(SummaryIt);
continue;
}
} else {
// If the current input contexts matches at least one of the
// contexts associated with the summary, drop it
if (std::any_of(Summary.MatchingContexts.begin(),
Summary.MatchingContexts.end(),
Predicate)) {
Remove = true;
break;
}
}
SummaryIt++;
}
if (Remove)
ContextIt = Contexts.erase(ContextIt);
else
ContextIt++;
}
// Do we have any contexts left?
if (Contexts.size() == 0) {
assert(!Changed);
return false;
}
// At this point we should only have a set of contexts which is worth to
// insert into the cache
// Prune all of the contexts and create a new summary
for (Element &Context : Contexts)
Context.prune(&Result, Indexer);
It->second.Summaries.push_back({ Contexts, Result, Temporary });
return Changed;
}
}
/// \brief Get the link register for the function identified by \p Function
GlobalVariable *getLinkRegister(BasicBlock *Function) const {
if (Default == nullptr)
return nullptr;
if (LinkRegisters.size() == 0) {
return Default;
} else {
auto It = LinkRegisters.find(Function);
if (It == LinkRegisters.end())
return Default;
else
return It->second;
}
}
/// \brief Initialize the link registers map, providing a default too
void setLinkRegisters(GlobalVariable *Default,
ReturnAddressesMap LinkRegisters) {
this->Default = Default;
this->LinkRegisters = LinkRegisters;
}
private:
// TODO: inline?
bool matches(const ASIndexer *Indexer,
const FunctionSummary &Summary,
const Element &Query) const {
Element Pruned = Query;
Pruned.prune(&Summary.Summary, Indexer);
for (const Element &Candidate : Summary.MatchingContexts)
if (Pruned.equal(Candidate))
return true;
return false;
}
private:
/// Map from function entry point to a vector of context and associated
/// analysis results
std::map<BasicBlock *, FunctionInfo> Results;
ReturnAddressesMap LinkRegisters;
std::set<BasicBlock *> FakeFunctions;
std::set<BasicBlock *> NoReturnFunctions;
GlobalVariable *Default;
};
/// \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 AnalysisInterrupt {
public:
enum BranchType {
InstructionLocalCFG, ///< Branch due to instruction-level CFG (e.g.,
/// conditional move)
FunctionLocalCFG, ///< Branch due to function-local CFG (a regular branch)
FakeFunctionCall, ///< A call to a fake function
FakeFunctionReturn, ///< A return from a fake function
HandledCall, ///< A function call for which the cache was able to produce a
/// summary
IndirectCall, ///< A function call for which the target is unknown
UnhandledCall, ///< A function call for which the cache was not able to
/// produce a summary (interprocedural part of the analysis
/// is required)
Return, ///< A proper function return
IndirectTailCall, ///< A branch representing an indirect tail call
FakeFunction, ///< This function is fake, inform the interprocedural part of
/// the analysis
LongJmp, ///< A branch representing a longjmp or similar constructs
Killer, ///< A killer basic block (killer syscall or endless loop)
Unreachable, ///< The basic block ends with an unreachable instruction
FunctionSummary, ///< The analysis of the function is finished and a summary
/// is available
NoReturnFunction ///< This is a function for which we couldn't find any
/// return statement
};
using vector = SmallVector<BasicBlock *, 2>;
private:
AnalysisInterrupt(ElementProxy Result, BranchType Type) :
Type(Type), Result(Result) { }
AnalysisInterrupt(ElementProxy Result,
BranchType Type,
SmallVector<BasicBlock *, 2> Successors) :
Type(Type), Result(Result), RelatedBasicBlocks(Successors) { }
public:
static AnalysisInterrupt
createWithSuccessor(ElementProxy Result,
BranchType Type,
BasicBlock * Successor) {
assert(Type == FakeFunctionCall
|| Type == HandledCall
|| Type == IndirectCall
|| Type == FakeFunctionReturn);
return AnalysisInterrupt(Result, Type, { Successor });
}
static AnalysisInterrupt
createWithSuccessors(ElementProxy Result,
BranchType Type,
SmallVector<BasicBlock *, 2> Successors) {
assert(Type == InstructionLocalCFG || Type == FunctionLocalCFG);
assert(Successors.size() > 0);
return AnalysisInterrupt(Result, Type, Successors);
}
static AnalysisInterrupt createNoReturnFunction() {
return create(ElementProxy(), NoReturnFunction);
}
static AnalysisInterrupt create(ElementProxy Result, BranchType Type) {
assert(Type == Return
|| Type == IndirectTailCall
|| Type == FakeFunction
|| Type == LongJmp
|| Type == Killer
|| Type == Unreachable
|| Type == NoReturnFunction);
return AnalysisInterrupt(Result, Type);
}
static AnalysisInterrupt
createUnhandledCall(ElementProxy Context, BasicBlock *Callee) {
return AnalysisInterrupt(Context, UnhandledCall, { Callee });
}
static AnalysisInterrupt createSummary(ElementProxy Result) {
return AnalysisInterrupt(Result, FunctionSummary, { });
}
public:
bool hasSuccessors() const {
switch (Type) {
case InstructionLocalCFG:
case FunctionLocalCFG:
case FakeFunctionCall:
case FakeFunctionReturn:
case HandledCall:
case IndirectCall:
return true;
case UnhandledCall:
case Return:
case IndirectTailCall:
case FakeFunction:
case LongJmp:
case Killer:
case Unreachable:
return false;
case FunctionSummary:
case NoReturnFunction:
default:
abort();
}
}
bool isFinalState() const {
switch (Type) {
case Return:
case IndirectTailCall:
// TODO: an IndirectTailCall, *can be* an IndirectTail call, but it might
// also just be a longjmp. So it's an assumption that this is a
// final state.
return true;
case InstructionLocalCFG:
case FunctionLocalCFG:
case FakeFunctionCall:
case FakeFunctionReturn:
case HandledCall:
case IndirectCall:
case UnhandledCall:
case FakeFunction:
case LongJmp:
case Killer:
case Unreachable:
return false;
case FunctionSummary:
case NoReturnFunction:
default:
abort();
}
}
BranchType type() const { return Type; }
bool requiresInterproceduralHandling() const {
switch (Type) {
case FakeFunction:
case UnhandledCall:
case FunctionSummary:
case NoReturnFunction:
return true;
case InstructionLocalCFG:
case FunctionLocalCFG:
case FakeFunctionCall:
case FakeFunctionReturn:
case HandledCall:
case IndirectCall:
case Return:
case IndirectTailCall:
case LongJmp:
case Killer:
case Unreachable:
return false;
default:
abort();
}
}
const ElementProxy &getResult() const {
assert(Type != FunctionSummary && Type != UnhandledCall);
return Result;
}
BasicBlock *getCallee() const {
assert(Type == UnhandledCall);
assert(RelatedBasicBlocks.size() == 1);
return RelatedBasicBlocks[0];
}
const Element &getCallContext() const {
assert(Type == UnhandledCall);
return Result.actual();
}
const ElementProxy &getFunctionSummary() const {
assert(Type == FunctionSummary);
return Result;
}
vector::const_iterator begin() {
assert(hasSuccessors());
return RelatedBasicBlocks.begin();
}
vector::const_iterator end() {
assert(hasSuccessors());
return RelatedBasicBlocks.end();
}
static StringRef getTypeName(BranchType Type) {
switch (Type) {
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 IndirectTailCall:
return "IndirectTailCall";
case FakeFunction:
return "FakeFunction";
case LongJmp:
return "LongJmp";
case Killer:
return "Killer";
case FunctionSummary:
return "FunctionSummary";
case NoReturnFunction:
return "NoReturnFunction";
default:
abort();
}
}
void dump(const Module *M) const {
dbg << "Interrupt reason: " << getTypeName(Type).str() << "\n";
switch (Type) {
case InstructionLocalCFG:
case FunctionLocalCFG:
case FakeFunctionCall:
case FakeFunctionReturn:
case HandledCall:
case IndirectCall:
dbg << "Successors:";
for (BasicBlock *BB : RelatedBasicBlocks)
dbg << " " << getName(BB);
dbg << "\n";
dbg << "Result:\n";
Result.dump(M);
break;
case UnhandledCall:
dbg << "Unhandled call to " << getName(RelatedBasicBlocks[0])
<< " with context:\n";
Result.dump(M);
break;
case Return:
case FunctionSummary:
case NoReturnFunction:
case IndirectTailCall:
case FakeFunction:
case LongJmp:
case Killer:
dbg << "Result:\n";
Result.dump(M);
break;
default:
abort();
}
}
private:
BranchType Type;
ElementProxy Result;
SmallVector<BasicBlock *, 2> RelatedBasicBlocks;
};
/// \brief Intraprocedural part of the stack analysis
class IntraproceduralAnalysis :
public MonotoneFramework<BasicBlock *,
ElementProxy,
AnalysisInterrupt,
IntraproceduralAnalysis> {
public:
IntraproceduralAnalysis(BasicBlock *Entry,
Cache &TheCache,
GeneratedCodeBasicInfo *GCBI,
Element InitialState) :
Entry(Entry),
TheCache(&TheCache),
GCBI(GCBI),
InitialState(InitialState),
Indexer(&TheCache.getIndexer(Entry)) {
const Module *M = getModule(Entry);
DBG("sa", {
dbg << "Creating IntraproceduralAnalysis for " << getName(Entry);
dbg << " with the follwing context\n";
this->InitialState.dump(M);
dbg << "\n";
});
TerminatorInst *T = Entry->getTerminator();
assert(T != nullptr);
// Obtain the link register used to call this function
LinkRegister = TheCache.getLinkRegister(Entry);
// Get the register indices for for the stack pointer, the program counter
// andn the link register
int32_t LinkRegisterIndex;
auto Indices = getCPUIndex<3>(getModule(Entry),
{
static_cast<const User *>(GCBI->pcReg()),
static_cast<const User *>(LinkRegister),
static_cast<const User *>(GCBI->spReg())
});
PCIndex = Indices[0];
LinkRegisterIndex = Indices[1];
SPIndex = Indices[2];
assert(PCIndex != -1
&& ((LinkRegisterIndex == -1) ^ (LinkRegister != nullptr)));
// Set the stack pointer to SP0+0
Value StackPointer(ASSlot(ASID::cpuID(), SPIndex));
ASID StackID = ASID::lastStackID();
this->InitialState.store(StackPointer, ASSlot(StackID, 0));
Top = this->InitialState.flatten();
if (LinkRegister == nullptr) {
auto StackID = ASID::lastStackID();
ReturnValue = this->InitialState.load(ASSlot(StackID, 0));
} else {
ReturnValue = this->InitialState.load(ASSlot(ASID::cpuID(),
LinkRegisterIndex));
}
DBG("sa", {
dbg << "The return address is in ";
if (LinkRegister != nullptr)
dbg << LinkRegister->getName().str();
else
dbg << "the top of the stack";
dbg << " and has value ";
ReturnValue.dump(M);
dbg << "\n";
});
DBG("sa-verbose", {
dbg << "ReturnValue: ";
ReturnValue.dump(M);
dbg << "\n";
});
initialize();
}
AnalysisInterrupt transfer(BasicBlock *BB);
/// \brief There's only on extremal label: the entry node
LabelRange extremalLabels() const { return { Entry }; }
/// \brief The extremal value, i.e., the context of the analysis
ElementProxy extremalValue() {
return ElementProxy(InitialState, Indexer);
}
using BranchType = AnalysisInterrupt::BranchType;
using BranchesTypeVector = std::vector<std::pair<BasicBlock *, BranchType>>;
BranchesTypeVector getBranchesType() const {
BranchesTypeVector Result;
std::copy(BranchesType.begin(),
BranchesType.end(),
std::back_inserter(Result));
return Result;
}
private:
/// \brief Part of the transfer function handling terminator instructions
AnalysisInterrupt
handleTerminator(TerminatorInst *T, ElementProxy &Result);
AnalysisInterrupt handleCall(BasicBlock *Callee,
uint64_t ReturnAddress,
BasicBlock *ReturnFromCall,
ElementProxy &Result);
private:
BasicBlock *Entry;
Cache *TheCache;
GlobalVariable *LinkRegister;
GeneratedCodeBasicInfo *GCBI;
std::set<uint64_t> FakeReturnAddresses;
Element InitialState;
Value ReturnValue;
ASSet Top;
int32_t SPIndex;
int32_t PCIndex;
ASIndexer *Indexer;
std::map<BasicBlock *, AnalysisInterrupt::BranchType> BranchesType;
std::map<Instruction *, Value> VariableContent;
};
/// \brief Is the basic block part of the function-local CFG?
///
/// Returns true if the basic block starts with a new instruction marker (a call
/// to newpc) or it's a dispatcher-related basic block
static bool isInstructionLocal(BasicBlock *BB) {
assert(!BB->empty());
if (isCallTo(&*BB->begin(), "newpc"))
return false;
return BB->getTerminator()->getMetadata("revamb.block.type") == nullptr;
}
AnalysisInterrupt
IntraproceduralAnalysis::handleCall(BasicBlock *Callee,
uint64_t ReturnAddress,
BasicBlock *ReturnFromCall,
ElementProxy &Result) {
using SAI = AnalysisInterrupt;
int32_t PCRegSize = GCBI->pcRegSize();
Value StackPointer(ASSlot(ASID::cpuID(), SPIndex));
Value PC(ASSlot(ASID::cpuID(), PCIndex));
const Module *M = getModule(Entry);
bool IsIndirect = Callee == nullptr;
bool IsIndirectTailCall = IsIndirect && ReturnFromCall == nullptr;
// Save the value of the stack pointer for later
Value OldStackPointer = Result.load(StackPointer);
//
// Prepare the context for querying the cache
//
Element CallContext = Result.actual();
// We have to transform the context into the actual state of this
// function. Shift all the address space indices by one position
CallContext.shiftAddressSpaces(+1);
// We now have to initialize some fields of the new state. Specifically,
// we have to set the stack pointer to SP0+0 and copy and flip the
// caller's address space into the new SP0 address space.
ASID StackID = ASID::lastStackID();
ASID CallerStackID = CallContext.getCallerStack(StackID);
// If we know the current stack frame size, copy the arguments
// TODO: report failure somehow
if (const ASSlot *OldStackPointerSlot = OldStackPointer.getASSlot()) {
assert(OldStackPointerSlot->addressSpace() == StackID);
int32_t CallerStackSize = -OldStackPointerSlot->offset();
assert(CallerStackSize >= 0);
CallContext.copyStackArguments(CallerStackID, StackID, CallerStackSize);
}
// Set the stack pointer to SP0+0
CallContext.store(StackPointer, ASSlot(StackID, 0));
// The context is now ready
LoadStoreLog CallSummary;
// Is it an indirect function call?
if (IsIndirect) {
// We have an indirect call, we assume top except for the stack pointer
// and the program counter being restored as expected
ElementProxy IndirectCallResult(CallContext, Result.indexer());
// Set to top
IndirectCallResult.top();
CallSummary = std::move(IndirectCallResult.log());
} else {
// We have a direct call
assert(Callee != nullptr);
// It's a direct function call, lookup the <Callee, Context> pair in the
// cache
Optional<LoadStoreLog> CacheEntry = TheCache->get(Callee, CallContext);
if (CacheEntry) {
// We have a match in the cache
CallSummary = std::move(CacheEntry.getValue());
} else {
// We don't have a match in the cache. Ask interprocedural analysis to
// analyze this function call with the current context
return SAI::createUnhandledCall(ElementProxy(CallContext, nullptr),
Callee);
}
}
DBG("sa", {
dbg << "The summary result for a call to " << getName(Callee)
<< " with the following context\n";
CallContext.dump(M);
dbg << "is\n";
CallSummary.dump(M);
dbg << "\n";
});
ASIndexer &OldIndexer = TheCache->getIndexer(Callee);
ASIndexer NewIndexer = CallSummary.moveToCaller(CallContext, OldIndexer);
// Use the result and proceed from the return address
Result.applyStores(CallSummary.Store);
Result.applyLoads(NewIndexer, CallSummary.Load);
// 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(ASID::globalID(), ReturnAddress);
Result.store(PC, Value(ReturnAddressSlot));
if (IsIndirectTailCall) {
return SAI::create(Result, SAI::IndirectTailCall);
} else {
assert(ReturnFromCall != nullptr);
auto Reason = IsIndirect ? SAI::IndirectCall : SAI::HandledCall;
return SAI::createWithSuccessor(Result, Reason, ReturnFromCall);
}
}
AnalysisInterrupt
IntraproceduralAnalysis::handleTerminator(TerminatorInst *T,
ElementProxy &Result) {
assert(!isa<UnreachableInst>(T));
using SAI = AnalysisInterrupt;
int32_t PCRegSize = GCBI->pcRegSize();
Value StackPointer(ASSlot(ASID::cpuID(), SPIndex));
Value PC(ASSlot(ASID::cpuID(), PCIndex));
// 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
auto NoReturnMD = T->getMetadata("noreturn");
if (auto *NoreturnTuple = dyn_cast_or_null<MDTuple>(NoReturnMD)) {
QuickMetadata QMD(getContext(T));
auto NoReturnType = QMD.extract<StringRef>(NoreturnTuple, 0);
if (NoReturnType != "LeadsToKiller")
return SAI::create(Result, SAI::Killer);
}
// 1. Check if we're dealing with instruction-local control flow (e.g., the if
// generated due to a conditional branch)
// 2. Check if it's an indirect branch, which means that "anypc" is among
// successors
bool IsInstructionLocal = false;
bool IsIndirect = false;
bool IsUnresolvedIndirect = false;
for (BasicBlock *Successor : T->successors()) {
// If at least one successor is not a jump target, the branch is instruction
// local
IsInstructionLocal = IsInstructionLocal || isInstructionLocal(Successor);
BlockType SuccessorType = GCBI->getType(Successor->getTerminator());
IsIndirect = IsIndirect
|| SuccessorType == AnyPCBlock
|| SuccessorType == UnexpectedPCBlock
|| SuccessorType == DispatcherBlock;
IsUnresolvedIndirect = IsUnresolvedIndirect
|| SuccessorType == AnyPCBlock
|| SuccessorType == DispatcherBlock;
}
if (IsInstructionLocal) {
SmallVector<BasicBlock *, 2> Successors;
std::copy(T->successors().begin(),
T->successors().end(),
std::back_inserter(Successors));
return SAI::createWithSuccessors(Result,
SAI::InstructionLocalCFG,
Successors);
}
// 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;
uint64_t ReturnAddress = 0;
auto It = T->getIterator();
if (It != T->getParent()->begin()) {
It--;
// TODO: we'd probably need something like prevNonMarker
if (CallInst *Call = getCallTo(&*It, "function_call")) {
IsFunctionCall = true;
auto *Arg0 = Call->getArgOperand(0);
auto *Arg1 = Call->getArgOperand(1);
auto *Arg2 = Call->getArgOperand(2);
if (auto *CalleeBlockAddress = dyn_cast<BlockAddress>(Arg0))
Callee = CalleeBlockAddress->getBasicBlock();
auto *ReturnBlockAddress = cast<BlockAddress>(Arg1);
ReturnFromCall = ReturnBlockAddress->getBasicBlock();
ReturnAddress = getLimitedValue(Arg2);
}
}
// 4. Check if the stack pointer is in its original position
bool IsInitialStack = false;
bool IsPostReturnStack = false;
// 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.getASSlot();
if (StackPointerSlot != nullptr
&& StackPointerSlot->addressSpace() == ASID::lastStackID()) {
// Check if we're exactly at same position as at the entry point
IsInitialStack = StackPointerSlot->offset() == 0;
if (LinkRegister == nullptr) {
// The return address is pushed on the stack: the post return stack should
// be the original one plus the size of the PC register
IsPostReturnStack = StackPointerSlot->offset() == PCRegSize;
} else {
// We have an actual link register: the post return stack should be the
// same as the initial one
IsPostReturnStack = IsInitialStack;
}
}
// 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(ASSlot(ASID::cpuID(), PCIndex));
Value ProgramCounterValue = Result.load(ProgramCounter);
// Do we have a named value as content?
if (const TaggedValue *PCContent = ProgramCounterValue.directContent()) {
const Tag *T = PCContent->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 or if the initial value of the
// link register had no indirect content and the direct content is an
// ASSlot
if (const TaggedValue *Return = ReturnValue.directContent()) {
const Tag *ReturnTag = Return->tag();
IsReturn = ReturnTag != nullptr
&& T != nullptr
&& *ReturnTag == *T;
if (const ASSlot *ReturnASSlot = ReturnValue.getASSlot()) {
if (const ASSlot *PCASSlot = PCContent->getASSlot()) {
IsReturn |= *ReturnASSlot == *PCASSlot;
}
}
}
if (!IsReturn) {
if (const ASSlot *ASSlotValue = PCContent->getASSlot()) {
if (ASSlotValue->addressSpace() == ASID::globalID()) {
uint32_t Offset = ASSlotValue->offset();
FakeFunctionReturnAddress = Offset;
IsReturnFromFake = FakeReturnAddresses.count(Offset) != 0;
}
}
}
}
}
// 6. Using the collected information classify the branch type
// Are we returning to the return address?
if (IsReturn) {
// Is the stack in the position it should be?
if (IsPostReturnStack) {
// This looks like an actual return
return SAI::create(Result, SAI::Return);
} else {
// We have a return instruction, but the stack is not in the position we'd
// expect, mark this function as a fake function.
return SAI::create(Result, SAI::FakeFunction);
}
}
if (IsFunctionCall) {
// Is it a call to a fake function?
if (!IsIndirect) {
if (TheCache->isFakeFunction(Callee)) {
// Assume normal control flow (i.e., inline)
FakeReturnAddresses.insert(ReturnAddress);
return SAI::createWithSuccessor(Result, SAI::FakeFunctionCall, Callee);
} else if (TheCache->isNoReturnFunction(Callee)) {
return SAI::create(Result, SAI::Killer);
}
}
return handleCall(Callee, ReturnAddress, ReturnFromCall, Result);
}
// 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
BasicBlock *ReturnBB = GCBI->getBlockAt(FakeFunctionReturnAddress);
return SAI::createWithSuccessor(Result,
SAI::FakeFunctionReturn,
ReturnBB);
}
// Check if it's a real indirect jump, i.e. we're not 100% of the targets
if (IsUnresolvedIndirect) {
if (IsInitialStack) {
// If the stack is not in a valid position, we consider it an indirect
// tail call
return handleCall(nullptr, 0, nullptr, Result);
} else {
// We have an indirect jump with a stack not ready to return, It'a a
// longjmp
return SAI::create(Result, SAI::LongJmp);
}
}
}
// The branch is direct and has nothing else special, consider it
// function-local
SmallVector<BasicBlock *, 2> Successors;
for (BasicBlock *Successor : T->successors())
if (GCBI->getType(Successor) != UnexpectedPCBlock)
Successors.push_back(Successor);
return SAI::createWithSuccessors(Result, SAI::FunctionLocalCFG, Successors);
}
AnalysisInterrupt IntraproceduralAnalysis::transfer(BasicBlock *BB) {
ElementProxy Result = State[BB];
const Module *M = getModule(BB);
DBG("sa", {
dbg << "Analyzing basic block " << getName(BB);
dbg << "\n";
Result.actual().dump(M);
dbg << "\n";
});
// TODO: prune all the info about dead instructions
BasicBlockState BBState(VariableContent,
M->getDataLayout(),
Result.actual().last());
for (Instruction &I : *BB) {
DBG("sa-verbose", {
dbg << "NewInstruction: ";
I.dump();
});
switch (I.getOpcode()) {
case Instruction::Load:
{
auto *Load = cast<LoadInst>(&I);
const Value &AddressValue = BBState.get(Load->getPointerOperand());
BBState.set(&I, Result.load(AddressValue));
break;
}
case Instruction::Store:
{
auto *Store = cast<StoreInst>(&I);
Value Address = BBState.get(Store->getPointerOperand());
Value StoredValue = BBState.get(Store->getValueOperand());
Result.store(Address, StoredValue);
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. 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<ConstantInt>(I.getOperand(1))) {
uint64_t Mask = getLimitedValue(SecondOperand);
uint64_t SignificantPCBits;
if (GCBI->pcRegSize() == 4) {
SignificantPCBits = std::numeric_limits<uint32_t>::max();
} else {
assert(GCBI->pcRegSize() == 8);
SignificantPCBits = std::numeric_limits<uint64_t>::max();
}
uint64_t AlignmentMask = GCBI->instructionAlignment() - 1;
SignificantPCBits = SignificantPCBits & ~AlignmentMask;
if ((SignificantPCBits & Mask) == SignificantPCBits) {
BBState.set(&I, FirstOperand);
break;
}
}
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<ConstantInt>(I.getOperand(1))) {
if (FirstOperand.add(Sign * getLimitedValue(Addend))) {
BBState.set(&I, FirstOperand);
break;
}
}
BBState.handleGenericInstruction(&I);
break;
}
case Instruction::Call:
// TODO: handle calls to helpers, we should detect where they could write
if (!cast<CallInst>(&I)->getFunctionType()->getReturnType()->isVoidTy())
BBState.set(&I, Top);
if (!isMarker(&I))
handleCall(nullptr, 0, nullptr, Result);
break;
case Instruction::Br:
case Instruction::Switch:
{
auto *T = cast<TerminatorInst>(&I);
AnalysisInterrupt BBResult = handleTerminator(T, Result);
BranchesType[BB] = BBResult.type();
std::set<BasicBlock *> ToReanalyze = BBState.save();
for (BasicBlock *BB : ToReanalyze)
registerToVisit(BB);
DBG("sa", {
dbg << "Basic block terminated: " << getName(BB) << "\n";
BBResult.dump(M);
dbg << "\n";
});
return BBResult;
break;
}
case Instruction::Unreachable:
{
using SAI = AnalysisInterrupt;
return SAI::create(Result, SAI::Unreachable);
}
default:
BBState.handleGenericInstruction(&I);
break;
}
assert(Result.verify());
}
assert(false);
}
/// \brief Interprocedural part of the stack analysis
class InterproceduralAnalysis {
private:
/// \brief Entry of the stack of the functions being currently analyzed
///
/// For each function currently being analyzed, it keeps track of the original
/// context which was used to enqueue it, its possibly enlarged version (e.g.,
/// due to recursion), the function entry point and the state of the
/// intraprocedural analysis so it can be resumed.
struct WorkItem {
WorkItem(Element Context,
BasicBlock *Function,
Cache &TheCache,
GeneratedCodeBasicInfo &GCBI) :
Context(Context),
MatchingContexts({ Context }),
Function(Function),
State(Function, TheCache, &GCBI, Context) { }
Element Context;
SmallVector<Element, 1> MatchingContexts;
BasicBlock *Function;
IntraproceduralAnalysis State;
bool verify(LoadStoreLog *Log=nullptr, ASIndexer *Indexer=nullptr) const {
// Check that the combination of the matching context is the context we
// used to compute the analysis
Element Combined = MatchingContexts[0];
auto Next = skip(1, MatchingContexts);
for (const Element &MatchingContext : Next)
Combined.combine(MatchingContext);
if (!Combined.equal(Context))
return false;
if (Log != nullptr) {
assert(Indexer != nullptr);
// Check that the combination of the pruned matching context is the
// pruned context we used to compute the analysis
auto Prune = [Indexer, Log] (Element L) {
L.prune(Log, Indexer);
return L;
};
Element PrunedContext = Prune(Context);
Combined = Prune(MatchingContexts[0]);
for (const Element &Context : skip(1, MatchingContexts)) {
Combined.combine(Prune(Context));
}
if (!Combined.equal(PrunedContext))
return false;
}
return true;
}
};
public:
InterproceduralAnalysis(Cache &TheCache,
GeneratedCodeBasicInfo &GCBI,
const Module *M) :
TheCache(TheCache), GCBI(GCBI) { }
class Result {
private:
enum FunctionType {
Regular,
Fake,
NoReturn
};
public:
using BranchesTypeVector = IntraproceduralAnalysis::BranchesTypeVector;
private:
Result(BranchesTypeVector BranchesType,
std::set<ASSlot> CalleeSaved,
FunctionType Type) :
BranchesType(BranchesType),
CalleeSaved(CalleeSaved),
Type(Type) { }
public:
static Result createFake(BranchesTypeVector BranchesType) {
return Result(BranchesType, { }, Fake);
}
static Result createNoReturn(BranchesTypeVector BranchesType) {
return Result(BranchesType, { }, NoReturn);
}
static Result createRegular(BranchesTypeVector BranchesType,
std::set<ASSlot> CalleeSaved) {
return Result(BranchesType, CalleeSaved, Regular);
}
const BranchesTypeVector &branchesType() const {
return BranchesType;
}
const std::set<ASSlot> &calleeSaved() const {
assert(isRegular());
return CalleeSaved;
}
bool isRegular() const { return Type == Regular; }
bool isFake() const { return Type == Fake; }
bool isNoReturn() const { return Type == NoReturn; }
private:
BranchesTypeVector BranchesType;
std::set<ASSlot> CalleeSaved;
FunctionType Type;
};
Result run(BasicBlock *Entry) {
using Result = InterproceduralAnalysis::Result;
assert(InProgress.size() == 0);
const Module *M = getModule(Entry);
// Prepare the context of the function being analyzed: spread all the
// initial address spaces (GLB, CPU and RST) with an ASSet of GLB and RST.
ASSet InitialContent;
InitialContent.add(ASID::globalID());
InitialContent.add(ASID::restOfTheStackID());
ASSet Address;
Address.add(ASID::globalID());
Address.add(ASID::cpuID());
Address.add(ASID::restOfTheStackID());
auto Indices = getCPUIndex<1>(M,
{ static_cast<const User *>(GCBI.spReg()) });
unsigned SPIndex = Indices[0];
Value StackPointer(ASSlot(ASID::cpuID(), SPIndex));
Element InitialContext;
InitialContext.store(Value(Address), Value(InitialContent));
ASID StackID = ASID::lastStackID();
// Set the stack pointer to SP0+0
InitialContext.store(StackPointer, ASSlot(StackID, 0));
newFunction(Entry, InitialContext);
while (true) {
assert(!InProgress.empty());
// Get an element from the queue (but don't pop)
WorkItem &Current = InProgress.back();
DBG("sa-interp", {
dbg << "Analyzing " << getName(Current.Function) << "\n";
});
// Run/continue the intraprocedural analysis
AnalysisInterrupt Result = Current.State.run();
assert(Result.requiresInterproceduralHandling());
switch(Result.type()) {
case AnalysisInterrupt::FakeFunction:
DBG("sa-interp", {
dbg << getName(Current.Function) << " is fake\n";
});
// It's a fake function, mark it as so and resume from the caller
TheCache.markAsFake(Current.Function);
if (InProgress.size() == 1)
return Result::createFake(Current.State.getBranchesType());
InProgress.pop_back();
break;
case AnalysisInterrupt::NoReturnFunction:
DBG("sa-interp", {
dbg << getName(Current.Function) << " does not return\n";
});
// It's a fake function, mark it as so and resume from the caller
TheCache.markAsNoReturn(Current.Function);
if (InProgress.size() == 1)
return Result::createNoReturn(Current.State.getBranchesType());
InProgress.pop_back();
break;
case AnalysisInterrupt::UnhandledCall:
{
const Element &CallContext = Result.getCallContext();
BasicBlock *Callee = Result.getCallee();
assert(Callee != nullptr);
DBG("sa-interp", {
dbg << getName(Current.Function)
<< " performs an unhandled call to "
<< getName(Callee) << "\n";
});
// Check if it's a recursive call
auto RecursionRootIt = InProgress.begin();
while (RecursionRootIt->Function != Callee
&& RecursionRootIt != InProgress.end()) {
RecursionRootIt++;
}
// Is it recursive?
if (RecursionRootIt != InProgress.end()) {
WorkItem &RecursionRoot = *RecursionRootIt;
DBG("sa-interp", {
dbg << getName(Current.Function) << " is recursive\n";
});
// Is the context of the recursive function call contained in the
// context of the root of the recursion?
if (CallContext.lowerThanOrEqual(RecursionRoot.Context)) {
// OK, we can proceed. We have to inject in the cache a temporary
// top entry for CallContext. If necessary, we also have to
// register this CallContext as compatible with RecursionRoot so
// that the cache can keep track of this.
// Register CallContext in RecursionRoot: the result of this
// analysis has to match also this context in the cache
assert(RecursionRoot.verify());
RecursionRoot.MatchingContexts.push_back(CallContext);
assert(RecursionRoot.verify());
DBG("sa-interp", {
dbg << "The context of the recursive call is large enough\n";
});
// Inject in the cache the <Result.Callee, RootContext, top>
IntraproceduralAnalysis Analysis(Callee,
TheCache,
&GCBI,
CallContext);
auto Top = Analysis.extremalValue();
Top.top();
assert(Top.verify());
TheCache.update(Callee,
RecursionRoot.MatchingContexts,
Top.log(),
true);
// At this point the intraprocedural analysis will resume
// employing bottom for the recursive call. Then, once 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 {
// The context is not contained in the root of recursion. Give up
// on everything, go back to the root of recursion and re-compute
// it with a wider context (the original one merged with the one
// of the recursive call)
DBG("sa-interp", {
dbg << "The context of the recursive call is ";
dbg << "NOT large enough\n";
});
// Merge the context of the recursive function call with the one
// from the root of the recursion
Element NewContext = CallContext;
NewContext.combine(RecursionRoot.Context);
DBG("sa", {
dbg << "Call context\n";
CallContext.dump(M);
dbg << "Recursion root context\n";
RecursionRoot.Context.dump(M);
dbg << "Result\n";
NewContext.dump(M);
});
assert(CallContext.lowerThanOrEqual(NewContext));
assert(RecursionRoot.Context.lowerThanOrEqual(NewContext));
// Pop everything from the top of tstack up until the root of the
// recursion excluded
InProgress.erase(++RecursionRootIt, InProgress.end());
assert(&RecursionRoot == &InProgress.back());
// Update the context associated to RecursionRoot with the widened
// one and register the context of this call as a mathcing context
assert(RecursionRoot.verify());
RecursionRoot.Context = NewContext;
RecursionRoot.MatchingContexts.push_back(CallContext);
assert(RecursionRoot.verify());
// Reset the intraprocedural analysis from where we will resume
// our analysis, using the widened context
RecursionRoot.State = IntraproceduralAnalysis(Callee,
TheCache,
&GCBI,
NewContext);
}
} else {
// Just a regular (uncached) function call, push it on the stack
newFunction(Callee, CallContext);
}
break;
}
case AnalysisInterrupt::FunctionSummary:
{
ElementProxy Summary = Result.getFunctionSummary();
LoadStoreLog Log = Summary.log();
auto &Indexer = TheCache.getIndexer(Current.Function);
DBG("sa-interp", {
dbg << "We have a summary for "
<< getName(Current.Function) << "\n";
});
assert(Current.verify(&Log, &Indexer));
bool Changed = TheCache.update(Current.Function,
Current.MatchingContexts,
Log);
for (Element &Context : Current.MatchingContexts) {
if (!TheCache.get(Current.Function, Context).hasValue()) {
dbg << "Couldn't find an entry we just inserted for function "
<< getName(Current.Function)
<< " in the cache using context:\n";
Context.dump(M);
dbg << "\n";
abort();
}
}
DBG("sa", {
dbg << "Log for " << getName(Current.Function) << "\n";
Log.dump(getModule(Entry));
dbg << "Load log:\n";
Indexer.dump(Log.Load);
});
if (Changed) {
DBG("sa-interp", {
dbg << "We have an improvement, let's reanalyze\n";
});
// Something changed, reset and re-run the analysis
Current.State.initialize();
} else {
DBG("sa-interp", {
dbg << "No improvement over the last analysis, we're OK\n";
});
// Is this the last iteration?
if (InProgress.size() == 1) {
return Result::createRegular(Current.State.getBranchesType(),
Summary.computeCalleeSavedSlots());
}
// We're done here, let's go up one position in the stack
InProgress.pop_back();
}
break;
}
default:
assert(false);
}
}
}
private:
void newFunction(BasicBlock *Entry, Element Context) {
InProgress.emplace_back(Context, Entry, TheCache, GCBI);
}
private:
Cache &TheCache;
GeneratedCodeBasicInfo &GCBI;
std::vector<WorkItem> InProgress;
};
namespace StackAnalysis {
char StackAnalysis::ID = 0;
static RegisterPass<StackAnalysis> X("sa",
"Stack Analysis Pass",
true,
true);
bool StackAnalysis::runOnFunction(Function &F) {
std::vector<BasicBlock *> Functions;
for (BasicBlock &BB : F) {
TerminatorInst *T = BB.getTerminator();
assert(T != nullptr);
MDNode *Node = T->getMetadata("revamb.jt.reasons");
if (auto *Tuple = dyn_cast_or_null<MDTuple>(Node)) {
for (Metadata *ReasonMD : Tuple->operands()) {
if (cast<MDString>(ReasonMD)->getString() == "Callee"
|| cast<MDString>(ReasonMD)->getString() == "UnusedGlobalData") {
Functions.push_back(&BB);
break;
}
}
}
}
Cache TheCache;
//
// For each function call identify where the return address is being stored
//
const Module *M = F.getParent();
Function *FunctionCallFunction = M->getFunction("function_call");
if (!FunctionCallFunction->user_empty()) {
std::map<GlobalVariable *, unsigned> LinkRegisterStats;
std::map<BasicBlock *, GlobalVariable *> LinkRegistersMap;
for (User *U : FunctionCallFunction->users()) {
if (auto *Call = dyn_cast<CallInst>(U)) {
assert(isCallTo(Call, "function_call"));
auto *LinkRegister = dyn_cast<GlobalVariable>(Call->getArgOperand(3));
LinkRegisterStats[LinkRegister]++;
// The callee might be unknown
if (auto *BBA = dyn_cast<BlockAddress>(Call->getArgOperand(0))) {
BasicBlock *Callee = BBA->getBasicBlock();
assert(LinkRegistersMap.count(Callee) == 0
|| LinkRegistersMap[Callee] == LinkRegister);
LinkRegistersMap[Callee] = LinkRegister;
}
}
}
// Identify a default storage for the return address (the most common one)
GlobalVariable *Default = nullptr;
if (LinkRegisterStats.size() == 1) {
Default = LinkRegisterStats.begin()->first;
} else {
std::pair<GlobalVariable *, unsigned> Max = { nullptr, 0 };
for (auto &P : LinkRegisterStats) {
if (P.second > Max.second)
Max = P;
}
assert(Max.first != nullptr && Max.second != 0);
Default = Max.first;
}
TheCache.setLinkRegisters(Default, std::move(LinkRegistersMap));
}
// Run the analysis
std::stringstream Output;
for (BasicBlock *BB : Functions) {
Output << "Function " << getName(BB);
auto &GCBI = getAnalysis<GeneratedCodeBasicInfo>();
InterproceduralAnalysis SA(TheCache, GCBI, M);
InterproceduralAnalysis::Result Result = SA.run(BB);
if (Result.isFake())
Output << " is fake";
else if (Result.isNoReturn())
Output << " does not return";
Output << "\n";
Output << " BranchesType:";
auto Sorted = Result.branchesType();
using P = std::pair<BasicBlock *, AnalysisInterrupt::BranchType>;
std::sort(Sorted.begin(),
Sorted.end(),
[] (const P &LHS, const P &RHS) {
return LHS.first->getName() < RHS.first->getName();
});
for (const P &Pair : Sorted) {
Output << " " << getName(Pair.first) << " (";
Output << AnalysisInterrupt::getTypeName(Pair.second).str() << ")";
}
Output << "\n";
if (Result.isRegular()) {
Output << " Saved slots:";
for (ASSlot Slot : Result.calleeSaved()) {
Output << " ";
Slot.dump(M, Output);
}
Output << "\n";
}
Output << "\n";
}
TextRepresentation = Output.str();
return false;
}
}