Files
revng-revng/lib/StackAnalysis/cache.cpp
T
Alessandro Di Federico 7fe00c08dd Rewrite the stack and introduce the ABI analyses
This is a very large commit importing the reviewed (and heavily
simplified) stack analysis and the new ABI analysis, which provides
information on the calling convention of each function and so on.

For an overview of the new analyses please consult OVERVIEW.md.
2018-09-18 15:58:20 +02:00

362 lines
11 KiB
C++

/// \file cache.cpp
/// \brief
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
// Local includes
#include "cache.h"
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");
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<GlobalVariable>(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<StoreInst>(&*It))
if (Store->getPointerOperand() == Address)
return false;
return true;
}
static bool mayAlias(const llvm::Value *A, const llvm::Value *B) {
return not ((isa<GlobalVariable>(A) or isa<GlobalVariable>(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<StoreInst>(&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<Instruction>(U.getUser());
if (UserI == nullptr || UserI->getParent()->getParent() != F)
continue;
if (const auto *Store = dyn_cast<StoreInst>(U.getUser())) {
if (U.getOperandNo() != StoreInst::getPointerOperandIndex())
continue;
// We have a store
const llvm::Value *ToStoreValue = Store->getValueOperand();
const auto *ToStore = dyn_cast<BinaryOperator>(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<const llvm::Value *> 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<LoadInst>(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)) {
assert(LoadFromSame == nullptr
or areEquivalent(LoadFromSame, TheLoad));
LoadFromSame = TheLoad;
}
// In any case stop
break;
} else if (const auto *BinOp = dyn_cast<BinaryOperator>(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<Constant>(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<Constant>(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<StoreInst>(&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<LoadInst>(Operand)) {
if (Load->getPointerOperand() == Address
and noWritesTo(Load, Store, Address))
IdentityStores.insert(Store);
} else if (auto *ZExt = dyn_cast<llvm::ZExtInst>(Operand)) {
NextOperand = ZExt->getOperand(0);
} else if (auto *Trunc = dyn_cast<llvm::TruncInst>(Operand)) {
if (Trunc->getType()->getIntegerBitWidth() >= StoreSize)
NextOperand = Trunc->getOperand(0);
}
}
} else if (const auto *Load = dyn_cast<LoadInst>(&I)) {
bool IsIdentityStore = true;
bool AtLeastOneStore = false;
std::set<const Use *> Visited;
std::queue<const Use *> 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<StoreInst>(TheUser)) {
Proceed = (I->getOperandNo() == 0
and TheStore->getPointerOperand() == Address
and noWritesTo(Load, TheStore, Address));
AtLeastOneStore = true;
} else if (isa<SelectInst>(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<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.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<GlobalVariable *, unsigned> Max = { nullptr, 0 };
for (auto &P : LinkRegisterStats) {
if (P.second > Max.second)
Max = P;
}
assert(Max.first != nullptr && Max.second != 0);
DefaultLinkRegister = Max.first;
}
}
}
Cache::Cache(const Function *F) : DefaultLinkRegister(nullptr) {
identifyPartialStores(F);
identifyIdentityLoads(F);
identifyLinkRegisters(F->getParent());
// Dump the results
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<const IntraproceduralFunctionSummary *>
Cache::get(BasicBlock *Function) const {
auto It = Results.find(Function);
if (It != Results.end())
return { &It->second };
return Optional<const IntraproceduralFunctionSummary *>();
}
bool Cache::update(BasicBlock *Function,
const IntraproceduralFunctionSummary &Result) {
DBG("sa", {
dbg << "Cache.update(" << getName(Function) << ") with value\n";
Result.dump(getModule(Function));
dbg << "\n";
});
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.
assert(New.lowerThanOrEqual(Old));
It->second = Result.copy();
return not Old.lowerThanOrEqual(New);
}
}
} // namespace StackAnalysis