mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
a1120e4caf
The stack analysis identifies CSV as `CPU+x` where `x` is an index that uniquely identifies a CSV. We used to compute this index multiple times, going through the list of global variables. After we switched from metadata to global variables for strings representing disassembled instructions, such process became very slow to the point of being a bottleneck due to the large amount of global variables. This commit precomputes, once and for all, the unique identifier of each CSV and saves it in a `std::set`.
746 lines
25 KiB
C++
746 lines
25 KiB
C++
/// \file interprocedural.cpp
|
|
/// \brief Implementation of the interprocedural portion of the stack analysis
|
|
|
|
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
//
|
|
|
|
// Standard includes
|
|
#include <chrono>
|
|
|
|
// Local libraries includes
|
|
#include "revng/Support/Statistics.h"
|
|
|
|
// Local includes
|
|
#include "Cache.h"
|
|
#include "InterproceduralAnalysis.h"
|
|
|
|
using llvm::BasicBlock;
|
|
using llvm::GlobalVariable;
|
|
using llvm::Instruction;
|
|
using llvm::Module;
|
|
using llvm::Optional;
|
|
using llvm::StringRef;
|
|
|
|
using time_point = std::chrono::steady_clock::time_point;
|
|
using StringIntCounter = CounterMap<std::string, uint64_t>;
|
|
|
|
Logger<> SaInterpLog("sa-interp");
|
|
|
|
/// \brief Logger for counting how much time is spent on a function
|
|
static StringIntCounter FunctionAnalysisTime("FunctionAnalysisTime");
|
|
|
|
/// \brief Logger for counting how many times a function is analyzed
|
|
static StringIntCounter FunctionAnalysisCount("FunctionAnalysisCount");
|
|
|
|
template<typename T>
|
|
static uint64_t nanoseconds(T Span) {
|
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(Span).count();
|
|
}
|
|
|
|
namespace StackAnalysis {
|
|
|
|
void InterproceduralAnalysis::push(BasicBlock *Entry) {
|
|
InProgressFunctions.insert(Entry);
|
|
InProgress.emplace_back(Entry,
|
|
TheCache,
|
|
&GCBI,
|
|
InProgressFunctions,
|
|
AnalyzeABI);
|
|
FunctionAnalysisCount.push(Entry->getName().str());
|
|
}
|
|
|
|
void InterproceduralAnalysis::run(BasicBlock *Entry, ResultsPool &Results) {
|
|
using IFS = IntraproceduralFunctionSummary;
|
|
|
|
revng_assert(InProgress.size() == 0);
|
|
|
|
Optional<const IFS *> Cached = TheCache.get(Entry);
|
|
|
|
// Has this function been analyzed already? If so, only now we register it in
|
|
// the ResultsPool.
|
|
if (Cached) {
|
|
FunctionType::Values Type;
|
|
if (TheCache.isFakeFunction(Entry))
|
|
Type = FunctionType::Fake;
|
|
else if (TheCache.isIndirectTailCall(Entry))
|
|
Type = FunctionType::IndirectTailCall;
|
|
else if (TheCache.isNoReturnFunction(Entry))
|
|
Type = FunctionType::NoReturn;
|
|
else
|
|
Type = FunctionType::Regular;
|
|
|
|
// Regular functions need to be composed by at least a basic block
|
|
const IFS &Summary = **Cached;
|
|
if (Type == FunctionType::Regular)
|
|
revng_assert(Summary.BranchesType.size() != 0);
|
|
|
|
Results.registerFunction(Entry, Type, Summary);
|
|
|
|
// We're done here
|
|
return;
|
|
}
|
|
|
|
// Setup logger: each time we start a new intraprocedural analysis we indent
|
|
// the output
|
|
SaInterpLog.setIndentation(0);
|
|
revng_log(SaInterpLog, "Running interprocedural analysis on " << Entry);
|
|
|
|
// Push the request function in the worklist
|
|
push(Entry);
|
|
|
|
FunctionType::Values Type = FunctionType::Invalid;
|
|
auto Result = Intraprocedural::Interrupt::createInvalid();
|
|
|
|
// Loop over the worklist
|
|
do {
|
|
Type = FunctionType::Invalid;
|
|
|
|
// Get an element from the stack (but don't pop yet)
|
|
Analysis &Current = InProgress.back();
|
|
|
|
SaInterpLog.setIndentation(InProgress.size());
|
|
revng_log(SaInterpLog,
|
|
"Analyzing function "
|
|
<< Current.entry() << " (size: " << Current.size()
|
|
<< " BBs, MustHit: " << Current.cacheMustHit() << ")");
|
|
SaInterpLog.indent();
|
|
|
|
time_point Begin = std::chrono::steady_clock::now();
|
|
|
|
// Run/continue the intraprocedural analysis
|
|
Result = Current.run();
|
|
|
|
time_point End = std::chrono::steady_clock::now();
|
|
FunctionAnalysisTime.push(Current.entry()->getName().str(),
|
|
nanoseconds(End - Begin));
|
|
|
|
revng_assert(Result.requiresInterproceduralHandling());
|
|
|
|
switch (Result.type()) {
|
|
case BranchType::FakeFunction:
|
|
revng_log(SaInterpLog, Current.entry() << " is fake");
|
|
|
|
// It's a fake function, mark it as so and resume from the caller
|
|
TheCache.markAsFake(Current.entry());
|
|
|
|
// Set the function type in case this is the last in the worklist
|
|
Type = FunctionType::Fake;
|
|
|
|
// If it was recursive, pop until the recurions root (excluded, for now)
|
|
if (const auto *Root = getRecursionRoot(Current.entry()))
|
|
popUntil(Root);
|
|
|
|
// Go back to the caller of the fake function.
|
|
pop();
|
|
|
|
// We basically evicted a function call which was supposed to hit the
|
|
// cache. Reset the flag.
|
|
Current.resetCacheMustHit();
|
|
|
|
break;
|
|
|
|
case BranchType::UnhandledCall: {
|
|
BasicBlock *Callee = Result.getCallee();
|
|
revng_assert(Callee != nullptr);
|
|
|
|
revng_log(SaInterpLog,
|
|
Current.entry() << " performs an unhandled call to " << Callee);
|
|
|
|
// Is it recursive?
|
|
if (getRecursionRoot(Callee) != nullptr) {
|
|
if (SaInterpLog.isEnabled()) {
|
|
SaInterpLog << Callee << " is recursive. Call stack:";
|
|
for (const Analysis &WorkItem : InProgress)
|
|
SaInterpLog << " " << WorkItem.entry();
|
|
SaInterpLog << DoLog;
|
|
}
|
|
|
|
// We now have to inject in the cache a temporary entry.
|
|
TheCache.update(Callee, IFS::bottom());
|
|
|
|
// Ensure it was inserted in the cache
|
|
revng_assert(TheCache.get(Callee));
|
|
|
|
// At this point the intraprocedural analysis will resume employing
|
|
// bottom for the recursive call. Then, once the analysis is done, the
|
|
// interprocedural part will detect that the result associated with it
|
|
// has changed (hopefully the result won't be bottom) and will run the
|
|
// analysis again until we're stable.
|
|
} else {
|
|
// Just a regular (uncached) function call, push it on the stack
|
|
push(Callee);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case BranchType::IndirectTailCallFunction:
|
|
case BranchType::NoReturnFunction:
|
|
case BranchType::FunctionSummary: {
|
|
const IFS &Summary = Result.getFunctionSummary();
|
|
|
|
revng_log(SaInterpLog, "We have a summary for " << Current.entry());
|
|
|
|
bool MustReanalyze = false;
|
|
|
|
// Are there function calls that lead to a contradiction?
|
|
const std::set<BasicBlock *> &Offending = Current.incoherentFunctions();
|
|
if (Offending.size() != 0) {
|
|
// If so, mark the called function as fake and re-analyze the caller
|
|
for (BasicBlock *Entry : Offending) {
|
|
revng_log(SaInterpLog,
|
|
Entry << " leads to contradiction, marking it as fake");
|
|
|
|
revng_assert(Current.entry() != Entry);
|
|
TheCache.markAsFake(Entry);
|
|
}
|
|
|
|
MustReanalyze = true;
|
|
} else {
|
|
// OK, no contradictions
|
|
|
|
// TODO: we should probably move the cleanup earlier on
|
|
// Perform some maintainance before recording in the cache
|
|
IFS SummaryForCache = Summary.copy();
|
|
SummaryForCache.FinalState.cleanup();
|
|
|
|
// Let's register the result in the cache and check if we got any
|
|
// changes w.r.t. to the last time we analyzed this function
|
|
MustReanalyze = TheCache.update(Current.entry(), SummaryForCache);
|
|
|
|
revng_assert(TheCache.get(Current.entry()));
|
|
}
|
|
|
|
if (SaLog.isEnabled()) {
|
|
revng_log(SaLog, "FinalState for " << getName(Current.entry()));
|
|
Summary.dump(getModule(Entry), SaLog);
|
|
}
|
|
|
|
if (MustReanalyze) {
|
|
revng_log(SaInterpLog, "Something has changed, let's reanalyze");
|
|
|
|
// Go back to the root of the recursion, if any
|
|
if (const auto *Root = getRecursionRoot(Current.entry()))
|
|
popUntil(Root);
|
|
|
|
// Something changed, reset and re-run the analysis
|
|
Current.initialize();
|
|
|
|
} else {
|
|
|
|
revng_log(SaInterpLog,
|
|
"No improvement over the last analysis, we're OK");
|
|
|
|
switch (Result.type()) {
|
|
case BranchType::IndirectTailCallFunction:
|
|
revng_log(SaInterpLog,
|
|
Current.entry() << " ends with an indirect tail call");
|
|
TheCache.markAsIndirectTailCall(Current.entry());
|
|
Type = FunctionType::IndirectTailCall;
|
|
break;
|
|
|
|
case BranchType::NoReturnFunction:
|
|
revng_log(SaInterpLog, Current.entry() << " doesn't return");
|
|
TheCache.markAsNoReturn(Current.entry());
|
|
Type = FunctionType::NoReturn;
|
|
break;
|
|
|
|
case BranchType::FunctionSummary:
|
|
Type = FunctionType::Regular;
|
|
break;
|
|
|
|
default:
|
|
revng_abort();
|
|
}
|
|
|
|
// We're done here, let's go up one position in the stack
|
|
pop();
|
|
}
|
|
|
|
} break;
|
|
|
|
case BranchType::InstructionLocalCFG:
|
|
case BranchType::FunctionLocalCFG:
|
|
case BranchType::FakeFunctionCall:
|
|
case BranchType::FakeFunctionReturn:
|
|
case BranchType::HandledCall:
|
|
case BranchType::IndirectCall:
|
|
case BranchType::Return:
|
|
case BranchType::IndirectTailCall:
|
|
case BranchType::LongJmp:
|
|
case BranchType::Killer:
|
|
case BranchType::Unreachable:
|
|
case BranchType::Invalid:
|
|
revng_abort("Unexpected branch type in interprocedural analysis");
|
|
}
|
|
|
|
} while (InProgress.size() > 0);
|
|
|
|
revng_assert(Type != FunctionType::Invalid);
|
|
Results.registerFunction(Entry, Type, Result.getFunctionSummary());
|
|
}
|
|
|
|
void ResultsPool::mergeFunction(BasicBlock *Function,
|
|
const IntraproceduralFunctionSummary &Summary) {
|
|
using FRA = FunctionRegisterArgument;
|
|
using FRV = FunctionReturnValue;
|
|
using FCRA = FunctionCallRegisterArgument;
|
|
using FCRV = FunctionCallReturnValue;
|
|
|
|
const Module *M = getModule(Function);
|
|
size_t CSVCount = std::distance(M->global_begin(), M->global_end());
|
|
|
|
LocallyWrittenRegisters[Function] = Summary.WrittenRegisters;
|
|
|
|
// Merge results from the arguments analyses
|
|
const FunctionABI &ABI = Summary.ABI;
|
|
auto &Slots = Summary.LocalSlots;
|
|
for (auto &Slot : Slots) {
|
|
int32_t Offset = Slot.first.offset();
|
|
FunctionSlot Key = { Function, Offset };
|
|
|
|
revng_assert(Slot.first.addressSpace() == ASID::cpuID()
|
|
and Offset <= static_cast<int32_t>(CSVCount));
|
|
|
|
switch (Slot.second) {
|
|
case LocalSlotType::UsedRegister:
|
|
case LocalSlotType::ForwardedArgument:
|
|
case LocalSlotType::ForwardedReturnValue:
|
|
ABI.applyResults(FunctionRegisterArguments[Key], Offset);
|
|
ABI.applyResults(FunctionReturnValues[Key], Offset);
|
|
|
|
// Handle forwarded arguments/return values (push rax; pop rdx)
|
|
if (Slot.second == LocalSlotType::ForwardedArgument
|
|
&& FunctionRegisterArguments[Key].value() == FRA::Yes) {
|
|
FunctionRegisterArguments[Key] = FRA::maybe();
|
|
}
|
|
|
|
if (Slot.second == LocalSlotType::ForwardedReturnValue
|
|
&& (FunctionReturnValues[Key].value() == FRV::Yes
|
|
|| FunctionReturnValues[Key].value() == FRV::YesCandidate)) {
|
|
FunctionReturnValues[Key] = FRV::maybe();
|
|
}
|
|
|
|
break;
|
|
|
|
case LocalSlotType::ExplicitlyCalleeSavedRegister:
|
|
FunctionRegisterArguments[Key] = FRA::no();
|
|
FunctionReturnValues[Key] = FRV::no();
|
|
ExplicitlyCalleeSavedRegisters[Function].insert(Slot.first.offset());
|
|
break;
|
|
}
|
|
|
|
for (auto &P : CallSites) {
|
|
CallSite Call = P.first;
|
|
|
|
if (!Call.belongsTo(Function))
|
|
continue;
|
|
|
|
FunctionCallSlot K = { Call, Offset };
|
|
Instruction *I = Call.callInstruction();
|
|
FunctionCall TheCall = { getFunctionCallCallee(I->getParent()), I };
|
|
|
|
switch (Slot.second) {
|
|
case LocalSlotType::UsedRegister:
|
|
case LocalSlotType::ForwardedArgument:
|
|
case LocalSlotType::ForwardedReturnValue:
|
|
|
|
ABI.applyResults(FunctionCallRegisterArguments[K], TheCall, Offset);
|
|
ABI.applyResults(FunctionCallReturnValues[K], TheCall, Offset);
|
|
|
|
// Handle forwarded arguments/return values (push rax; pop rdx)
|
|
if (Slot.second == LocalSlotType::ForwardedArgument
|
|
&& FunctionCallRegisterArguments[K].value() == FCRA::Yes) {
|
|
FunctionCallRegisterArguments[K] = FCRA::maybe();
|
|
}
|
|
|
|
if (Slot.second == LocalSlotType::ForwardedReturnValue
|
|
&& FunctionCallReturnValues[K].value() == FCRV::Yes) {
|
|
FunctionCallReturnValues[K] = FCRV::maybe();
|
|
}
|
|
|
|
break;
|
|
|
|
case LocalSlotType::ExplicitlyCalleeSavedRegister:
|
|
FunctionCallRegisterArguments[K] = FunctionCallRegisterArgument::no();
|
|
FunctionCallReturnValues[K] = FunctionCallReturnValue::no();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ResultsPool::mergeBranches(BasicBlock *Function,
|
|
const BasicBlockTypeMap &Branches) {
|
|
// Merge information about the branches type
|
|
for (auto &P : Branches)
|
|
BranchesType[{ Function, P.first->getTerminator() }] = P.second;
|
|
}
|
|
|
|
void ResultsPool::mergeCallSites(BasicBlock *Entry,
|
|
const StackSizeMap &ToImport) {
|
|
for (auto &P : ToImport) {
|
|
FunctionCalls[Entry].push_back(P.first);
|
|
CallSite Call = { Entry, P.first.callInstruction() };
|
|
auto It = CallSites.find(Call);
|
|
if (It != CallSites.end()) {
|
|
if (!compareOptional(It->second, P.second)) {
|
|
revng_abort("This call site has a stack at a different height than"
|
|
" previously recorded");
|
|
}
|
|
} else {
|
|
CallSites[Call] = P.second;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// \brief Helper class for computing the set of registers clobbered by each
|
|
/// function
|
|
///
|
|
/// This class basically takes each function, computes the set of all the
|
|
/// registers written by it and all of the functions in the transitive closure
|
|
/// of the callee set and remove the registers that are explicitly callee saved
|
|
/// (ECS).
|
|
///
|
|
/// This process is repeated multiple times to handle with increasing precision
|
|
/// recursive and indirect function calls, which are initially ignored. After
|
|
/// the first iteration, the recursive function calls use the result from the
|
|
/// previous iteration. On the other hand, indirect function calls are
|
|
/// considered clobbering all the registers except those that are ECS in the
|
|
/// majority of the functions.
|
|
struct ClobberedRegistersAnalysis {
|
|
using ClobberedMap = std::map<llvm::BasicBlock *, std::set<int32_t>>;
|
|
|
|
/// \brief Struct representing the result of a single iteration
|
|
struct IterationResult {
|
|
public:
|
|
/// \brief Description of a register
|
|
struct ECSVote {
|
|
public:
|
|
unsigned ECS; ///< Number of functions in which this register is ECS
|
|
unsigned Total; ///< Number of functions writing this register
|
|
|
|
public:
|
|
bool operator==(const ECSVote &Other) const {
|
|
return std::tie(ECS, Total) == std::tie(Other.ECS, Other.Total);
|
|
}
|
|
|
|
bool isECS() const { return ECS > Total / 2; }
|
|
};
|
|
|
|
public:
|
|
ClobberedMap Clobbered;
|
|
std::map<int32_t, ECSVote> ECSVotes;
|
|
|
|
public:
|
|
bool operator==(const IterationResult &Other) const {
|
|
using std::tie;
|
|
return tie(Clobbered, ECSVotes) == tie(Other.Clobbered, Other.ECSVotes);
|
|
}
|
|
|
|
bool operator!=(const IterationResult &Other) const {
|
|
return not(*this == Other);
|
|
}
|
|
};
|
|
|
|
/// \brief Compute an iteration
|
|
static IterationResult
|
|
recompute(ResultsPool &This, const ClobberedMap &InitialState) {
|
|
// The results pool
|
|
IterationResult Result;
|
|
auto &Clobbered = Result.Clobbered;
|
|
auto &ECSVotes = Result.ECSVotes;
|
|
|
|
// Loop over all the functions
|
|
for (auto &P : This.FunctionTypes) {
|
|
BasicBlock *Function = P.first;
|
|
|
|
// Have we handled this already?
|
|
if (Clobbered.count(Function) != 0)
|
|
continue;
|
|
|
|
using iterator = typename std::vector<FunctionCall>::const_iterator;
|
|
|
|
struct State {
|
|
public:
|
|
BasicBlock *Function;
|
|
iterator CallIt;
|
|
iterator EndCallIt;
|
|
|
|
public:
|
|
State(BasicBlock *Function, iterator CallIt, iterator EndCallIt) :
|
|
Function(Function),
|
|
CallIt(CallIt),
|
|
EndCallIt(EndCallIt) {}
|
|
};
|
|
|
|
// Worklist
|
|
std::vector<State> WorkList;
|
|
|
|
// Set of currently in-progress functions, used to detect recursive
|
|
// function calls
|
|
std::set<BasicBlock *> InProgress;
|
|
|
|
// Initialize the worklist
|
|
const auto &FunctionCallsList = This.FunctionCalls[Function];
|
|
WorkList.emplace_back(Function,
|
|
FunctionCallsList.begin(),
|
|
FunctionCallsList.end());
|
|
InProgress.insert(Function);
|
|
|
|
// Loop over the worklist
|
|
while (not WorkList.empty()) {
|
|
// Peek but don't pop
|
|
State &Current = WorkList.back();
|
|
BasicBlock *Function = Current.Function;
|
|
|
|
// Get a reference to the results for the current function
|
|
std::set<int32_t> &CurrentClobbered = Clobbered[Function];
|
|
|
|
// Loop over the unprocessed function calls
|
|
while (Current.CallIt != Current.EndCallIt) {
|
|
// Get the callee
|
|
BasicBlock *Callee = Current.CallIt->callee();
|
|
|
|
if ((Callee == nullptr) or (InProgress.count(Callee) != 0)) {
|
|
// Indirect or recursive function call, use result from last
|
|
// iteration
|
|
auto It = InitialState.find(Callee);
|
|
if (It != InitialState.end())
|
|
CurrentClobbered.insert(It->second.begin(), It->second.end());
|
|
|
|
} else {
|
|
// Do we already handle this callee?
|
|
auto ClobberedIt = Clobbered.find(Callee);
|
|
if (ClobberedIt == Clobbered.end()) {
|
|
// No, push it on the worklist
|
|
const auto &FunctionCallsList = This.FunctionCalls[Callee];
|
|
WorkList.emplace_back(Callee,
|
|
FunctionCallsList.begin(),
|
|
FunctionCallsList.end());
|
|
InProgress.insert(Callee);
|
|
|
|
// Early exit so we can proceed from the callee
|
|
break;
|
|
}
|
|
|
|
// OK, we already processed this callee
|
|
|
|
// Merge in the clobbered set all those clobbered by the callee
|
|
CurrentClobbered.insert(ClobberedIt->second.begin(),
|
|
ClobberedIt->second.begin());
|
|
}
|
|
|
|
// Proceed to the next call site
|
|
Current.CallIt++;
|
|
}
|
|
|
|
// Are we done?
|
|
if (Current.CallIt == Current.EndCallIt) {
|
|
// Oh, we're done
|
|
|
|
{
|
|
// Add all the locally written registers
|
|
auto It = This.LocallyWrittenRegisters.find(Function);
|
|
if (It != This.LocallyWrittenRegisters.end())
|
|
CurrentClobbered.insert(It->second.begin(), It->second.end());
|
|
}
|
|
|
|
// Increase the counter associated to each written register
|
|
for (int32_t Index : CurrentClobbered)
|
|
ECSVotes[Index].Total++;
|
|
|
|
{
|
|
// Erase from the clobbered registers all the callee-saved, if any
|
|
auto It = This.ExplicitlyCalleeSavedRegisters.find(Function);
|
|
if (It != This.ExplicitlyCalleeSavedRegisters.end()) {
|
|
// Do not use CurrentClobbered.erase(BeginIt, EndIt);
|
|
for (int32_t Index : It->second)
|
|
CurrentClobbered.erase(Index);
|
|
|
|
// Increase the counter associated to each register
|
|
for (int32_t Index : It->second)
|
|
ECSVotes[Index].ECS++;
|
|
}
|
|
}
|
|
|
|
// Pop from the worklist
|
|
WorkList.pop_back();
|
|
InProgress.erase(Function);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
/// \brief Repeat the analysis until a fixed point is reached
|
|
static ClobberedMap run(ResultsPool &This) {
|
|
IterationResult LastResult;
|
|
IterationResult NewResult;
|
|
|
|
do {
|
|
LastResult = std::move(NewResult);
|
|
|
|
// Use as initial state the previous iteration's state
|
|
NewResult = recompute(This, LastResult.Clobbered);
|
|
|
|
// Perform a majority vote on the result to associate to indirect function
|
|
// calls
|
|
std::set<int32_t> IndirectCallClobbered;
|
|
for (auto &P : NewResult.ECSVotes) {
|
|
|
|
// When a register is written, is it usually an ECS?
|
|
if (not P.second.isECS()) {
|
|
// No, consider it clobbered by indirect function calls
|
|
IndirectCallClobbered.insert(P.first);
|
|
}
|
|
}
|
|
|
|
// Assert the new set of registers clobbered by indirect function calls
|
|
// contains at least all of the registers clobbered in the previous
|
|
// iteration. If this is not the case, the algorithm might not converge.
|
|
for (int32_t Clobbered : LastResult.Clobbered[nullptr])
|
|
revng_assert(IndirectCallClobbered.count(Clobbered) != 0);
|
|
|
|
// Save the results of the vote as the result associated with nullptr
|
|
NewResult.Clobbered[nullptr] = std::move(IndirectCallClobbered);
|
|
|
|
} while (LastResult != NewResult);
|
|
|
|
return std::move(NewResult.Clobbered);
|
|
}
|
|
};
|
|
|
|
FunctionsSummary ResultsPool::finalize(const Module *M) {
|
|
ASID CPU = ASID::cpuID();
|
|
|
|
std::vector<const GlobalVariable *> IndexToCSV;
|
|
IndexToCSV.push_back(nullptr);
|
|
for (const GlobalVariable &CSV : M->globals())
|
|
IndexToCSV.push_back(&CSV);
|
|
|
|
FunctionsSummary Result;
|
|
|
|
// Set function types
|
|
for (auto &P : FunctionTypes)
|
|
Result.Functions[P.first].Type = P.second;
|
|
|
|
// Compute the set of registers clobbered by each function
|
|
ClobberedRegistersAnalysis::ClobberedMap Clobbered;
|
|
Clobbered = ClobberedRegistersAnalysis::run(*this);
|
|
for (auto &P : Clobbered) {
|
|
auto &Function = Result.Functions[P.first];
|
|
for (int32_t Offset : P.second)
|
|
Function.ClobberedRegisters.insert(IndexToCSV.at(Offset));
|
|
}
|
|
|
|
// Register block types
|
|
for (auto &P : BranchesType) {
|
|
BasicBlock *BB = P.first.branch()->getParent();
|
|
Result.Functions[P.first.entry()].BasicBlocks[BB] = P.second;
|
|
}
|
|
|
|
// Initialize the information about functions in Result
|
|
for (auto &P : FunctionRegisterArguments) {
|
|
auto &Function = Result.Functions[P.first.first];
|
|
const GlobalVariable *CSV = IndexToCSV.at(P.first.second);
|
|
Function.RegisterSlots[CSV].Argument = P.second;
|
|
}
|
|
|
|
for (auto &P : FunctionReturnValues) {
|
|
auto &Function = Result.Functions[P.first.first];
|
|
const GlobalVariable *CSV = IndexToCSV.at(P.first.second);
|
|
Function.RegisterSlots[CSV].ReturnValue = P.second;
|
|
}
|
|
|
|
// Collect, for each call site, all the slots
|
|
std::map<CallSite, std::set<ASSlot>> FunctionCallSlots;
|
|
for (auto &P : FunctionCallRegisterArguments) {
|
|
auto Slot = ASSlot::create(CPU, P.first.second);
|
|
FunctionCallSlots[P.first.first].insert(Slot);
|
|
}
|
|
for (auto &P : FunctionCallReturnValues) {
|
|
auto Slot = ASSlot::create(CPU, P.first.second);
|
|
FunctionCallSlots[P.first.first].insert(Slot);
|
|
}
|
|
|
|
//
|
|
// Merge information about a function and all the call sites targeting it
|
|
//
|
|
|
|
// For each function call
|
|
for (auto &P : FunctionCallSlots) {
|
|
CallSite Call = P.first;
|
|
revng_assert(CallSites.count(Call) != 0);
|
|
Instruction *I = Call.callInstruction();
|
|
BasicBlock *Callee = getFunctionCallCallee(I->getParent());
|
|
bool UnknownCallee = (Callee == nullptr);
|
|
|
|
// Register this call site among the call sites of the caller
|
|
auto &CallerCallSites = Result.Functions[Call.caller()].CallSites;
|
|
CallerCallSites.emplace_back(I, Callee);
|
|
auto &NewCallSite = CallerCallSites.back();
|
|
|
|
// For each slot in this function call
|
|
for (ASSlot Slot : P.second) {
|
|
int32_t Offset = Slot.offset();
|
|
FunctionCallSlot FCS{ Call, Offset };
|
|
|
|
revng_assert(Slot.addressSpace() == CPU);
|
|
FunctionSlot TheFunctionSlot{ Callee, Offset };
|
|
|
|
// Take the result of the analyses for the current call site
|
|
auto &FCRA = FunctionCallRegisterArguments;
|
|
revng_assert(FCRA.count(FCS) != 0);
|
|
const auto &CallerRegisterArgument = FCRA[FCS];
|
|
|
|
auto &FCRV = FunctionCallReturnValues;
|
|
revng_assert(FCRV.count(FCS) != 0);
|
|
const auto &CallerReturnValue = FCRV[FCS];
|
|
|
|
const GlobalVariable *CSV = IndexToCSV.at(Offset);
|
|
NewCallSite.RegisterSlots[CSV].Argument = CallerRegisterArgument;
|
|
NewCallSite.RegisterSlots[CSV].ReturnValue = CallerReturnValue;
|
|
|
|
// Check if the register is used at all in this function
|
|
auto &FRA = FunctionRegisterArguments;
|
|
auto &FRV = FunctionReturnValues;
|
|
auto &Register = Result.Functions[Callee].RegisterSlots[CSV];
|
|
bool CalleeHasSlot = FRA.count(TheFunctionSlot) != 0;
|
|
|
|
if (not UnknownCallee and CalleeHasSlot) {
|
|
// The register is used and the callee is available, collect the
|
|
// information on the called function
|
|
const auto &Argument = FRA[TheFunctionSlot];
|
|
const auto &ReturnValue = FRV[TheFunctionSlot];
|
|
|
|
// Update the information on the caller side incorporating those
|
|
// coming from the called function
|
|
NewCallSite.RegisterSlots[CSV].Argument.combine(Argument);
|
|
NewCallSite.RegisterSlots[CSV].ReturnValue.combine(ReturnValue);
|
|
|
|
// Update the information on the called function incorporating the
|
|
// information coming from the current call site
|
|
Register.Argument.combine(CallerRegisterArgument);
|
|
Register.ReturnValue.combine(CallerReturnValue);
|
|
} else {
|
|
// TODO: Closed World Assumption
|
|
auto &CallSiteRegister = NewCallSite.RegisterSlots[CSV];
|
|
|
|
// Either the callee is unknown or does not use this slot
|
|
CallSiteRegister.Argument.notAvailable();
|
|
CallSiteRegister.ReturnValue.notAvailable();
|
|
|
|
Register.Argument.combine(CallSiteRegister.Argument);
|
|
Register.ReturnValue.combine(CallSiteRegister.ReturnValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
} // namespace StackAnalysis
|