mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
1023 lines
35 KiB
C++
1023 lines
35 KiB
C++
/// \file InterproceduralAnalysis.cpp
|
|
/// \brief Implementation of the interprocedural portion of the stack analysis
|
|
|
|
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
//
|
|
|
|
#include <chrono>
|
|
|
|
#include "revng/Support/Statistics.h"
|
|
|
|
#include "InterproceduralAnalysis.h"
|
|
|
|
#include "Cache.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, skip it.
|
|
if (Cached)
|
|
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.
|
|
if (InProgress.size() > 0)
|
|
InProgress.back().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::NoReturnFunction:
|
|
case BranchType::RegularFunction: {
|
|
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::NoReturnFunction:
|
|
revng_log(SaInterpLog, Current.entry() << " doesn't return");
|
|
TheCache.markAsNoReturn(Current.entry());
|
|
Type = FunctionType::NoReturn;
|
|
break;
|
|
|
|
case BranchType::RegularFunction:
|
|
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::BrokenReturn:
|
|
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);
|
|
}
|
|
|
|
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;
|
|
FakeReturns[Function] = Summary.FakeReturns;
|
|
|
|
// 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::YesOrDead)) {
|
|
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:
|
|
case LocalSlotType::ExplicitlyCalleeSavedRegister:
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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::deque<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(Module *M, Cache *TheCache) {
|
|
ASID CPU = ASID::cpuID();
|
|
|
|
// Create the result data structure
|
|
FunctionsSummary Result;
|
|
|
|
// Set function types
|
|
for (auto &P : FunctionTypes)
|
|
Result.Functions[P.first].Type = P.second;
|
|
|
|
// Set function types
|
|
for (auto &P : FakeReturns)
|
|
Result.Functions[P.first].FakeReturns = 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(TheCache->getCSVByIndex(Offset));
|
|
}
|
|
|
|
// Register block types
|
|
for (auto &P : BranchesType) {
|
|
BasicBlock *BB = P.first.branch()->getParent();
|
|
Result.Functions[P.first.entry()].BasicBlocks[BB] = P.second;
|
|
}
|
|
|
|
using CallSiteDescription = FunctionsSummary::CallSiteDescription;
|
|
|
|
//
|
|
// Collect, for each call site, all the slots and create a CallSiteDescription
|
|
//
|
|
struct FunctionCallSites {
|
|
/// \brief Collect all the slots used by the function/its callers
|
|
std::set<ASSlot> Slots;
|
|
/// \brief The callers
|
|
std::map<CallSite, CallSiteDescription *> CallSites;
|
|
};
|
|
std::map<BasicBlock *, FunctionCallSites> FunctionCallSitesMap;
|
|
|
|
auto &FCRA = FunctionCallRegisterArguments;
|
|
auto &FCRV = FunctionCallReturnValues;
|
|
auto &FRA = FunctionRegisterArguments;
|
|
auto &FRV = FunctionReturnValues;
|
|
|
|
for (auto &P : FRA) {
|
|
BasicBlock *FunctionEntry = P.first.first;
|
|
FunctionCallSites &FCS = FunctionCallSitesMap[FunctionEntry];
|
|
auto Slot = ASSlot::create(CPU, P.first.second);
|
|
FCS.Slots.insert(Slot);
|
|
}
|
|
|
|
for (auto &P : FRV) {
|
|
BasicBlock *FunctionEntry = P.first.first;
|
|
FunctionCallSites &FCS = FunctionCallSitesMap[FunctionEntry];
|
|
auto Slot = ASSlot::create(CPU, P.first.second);
|
|
FCS.Slots.insert(Slot);
|
|
}
|
|
|
|
// Go over arguments of function calls
|
|
for (auto &P : FCRA) {
|
|
const CallSite &TheCallSite = P.first.first;
|
|
auto Slot = ASSlot::create(CPU, P.first.second);
|
|
BasicBlock *CallerBB = TheCallSite.callInstruction()->getParent();
|
|
BasicBlock *Callee = getFunctionCallCallee(CallerBB);
|
|
FunctionCallSites &FCS = FunctionCallSitesMap[Callee];
|
|
|
|
// Register the slot
|
|
FCS.Slots.insert(Slot);
|
|
|
|
// Check if we already created the CallSiteDescription
|
|
auto It = FCS.CallSites.find(TheCallSite);
|
|
if (It == FCS.CallSites.end()) {
|
|
auto &CallerCallSites = Result.Functions[TheCallSite.caller()].CallSites;
|
|
Instruction *I = TheCallSite.callInstruction();
|
|
CallerCallSites.emplace_back(I, Callee);
|
|
FCS.CallSites[TheCallSite] = &CallerCallSites.back();
|
|
revng_assert(FCS.CallSites[TheCallSite] == &CallerCallSites.back());
|
|
}
|
|
}
|
|
|
|
// Go over return values of function calls
|
|
for (auto &P : FCRV) {
|
|
const CallSite &TheCallSite = P.first.first;
|
|
auto Slot = ASSlot::create(CPU, P.first.second);
|
|
BasicBlock *CallerBB = TheCallSite.callInstruction()->getParent();
|
|
BasicBlock *Callee = getFunctionCallCallee(CallerBB);
|
|
FunctionCallSites &FCS = FunctionCallSitesMap[Callee];
|
|
FCS.Slots.insert(Slot);
|
|
}
|
|
|
|
//
|
|
// Merge information about a function and all the call sites targeting it
|
|
//
|
|
|
|
// For each function
|
|
for (auto &P : Result.Functions) {
|
|
BasicBlock *FunctionEntry = P.first;
|
|
|
|
// Integrate slots from each call site
|
|
FunctionCallSites &FCS = FunctionCallSitesMap[FunctionEntry];
|
|
|
|
// Iterate over each slot
|
|
for (ASSlot Slot : FCS.Slots) {
|
|
revng_assert(Slot.addressSpace() == CPU);
|
|
int32_t Offset = Slot.offset();
|
|
if (not TheCache->isCSVIndex(Offset))
|
|
continue;
|
|
|
|
GlobalVariable *CSV = TheCache->getCSVByIndex(Offset);
|
|
FunctionSlot TheFunctionSlot{ FunctionEntry, Offset };
|
|
|
|
bool CalleeHasSlot = FRA.count(TheFunctionSlot) != 0;
|
|
if (FunctionEntry == nullptr or not CalleeHasSlot) {
|
|
for (auto &Q : FCS.CallSites) {
|
|
CallSiteDescription &TheCallSiteDescription = *Q.second;
|
|
auto &CallSiteRegister = TheCallSiteDescription.RegisterSlots[CSV];
|
|
const CallSite &TheCallSite = Q.first;
|
|
FunctionCallSlot FCS{ TheCallSite, Offset };
|
|
|
|
CallSiteRegister.Argument = FCRA[FCS];
|
|
CallSiteRegister.Argument.notAvailable();
|
|
CallSiteRegister.ReturnValue = FCRV[FCS];
|
|
CallSiteRegister.ReturnValue.notAvailable();
|
|
|
|
if (FunctionEntry != nullptr) {
|
|
using FRegisterArgument = FunctionRegisterArgument;
|
|
using FReturnValue = FunctionReturnValue;
|
|
auto &Slot = P.second.RegisterSlots[CSV];
|
|
Slot.Argument = FRegisterArgument(FRegisterArgument::Maybe);
|
|
Slot.ReturnValue = FReturnValue(FReturnValue::Maybe);
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
{
|
|
//
|
|
// Merge arguments
|
|
//
|
|
|
|
// Register status at the function
|
|
const FunctionRegisterArgument &FunctionStatus = FRA[TheFunctionSlot];
|
|
auto Status = FunctionStatus.value();
|
|
revng_assert(Status == FunctionRegisterArgument::Maybe
|
|
or Status == FunctionRegisterArgument::NoOrDead
|
|
or Status == FunctionRegisterArgument::Contradiction
|
|
or Status == FunctionRegisterArgument::Yes
|
|
or Status == FunctionRegisterArgument::No);
|
|
|
|
// Propagate information from the function to callers (and record if for
|
|
// at least a call site we have Yes information before the merge)
|
|
bool AtLeastAYes = false;
|
|
|
|
for (auto &Q : FCS.CallSites) {
|
|
const CallSite &TheCallSite = Q.first;
|
|
revng_assert(Q.second != nullptr);
|
|
CallSiteDescription &TheCallSiteDescription = *Q.second;
|
|
FunctionCallSlot FCS{ TheCallSite, Offset };
|
|
|
|
// Register status at current call site
|
|
const FunctionCallRegisterArgument &CallerStatus = FCRA[FCS];
|
|
auto Status = CallerStatus.value();
|
|
revng_assert(Status == FunctionCallRegisterArgument::Maybe
|
|
or Status == FunctionCallRegisterArgument::Yes);
|
|
|
|
// Register if there's at least a Yes
|
|
AtLeastAYes = (AtLeastAYes
|
|
or Status == FunctionCallRegisterArgument::Yes);
|
|
|
|
// Update the status at the call site, starting from the status of the
|
|
// callee
|
|
FunctionCallRegisterArgument Result;
|
|
using FCRegisterArgument = FunctionCallRegisterArgument;
|
|
switch (FunctionStatus.value()) {
|
|
case FunctionRegisterArgument::Maybe:
|
|
Result = FCRegisterArgument(FCRegisterArgument::Maybe);
|
|
break;
|
|
case FunctionRegisterArgument::NoOrDead:
|
|
Result = FCRegisterArgument(FCRegisterArgument::NoOrDead);
|
|
break;
|
|
case FunctionRegisterArgument::Contradiction:
|
|
Result = FCRegisterArgument(FCRegisterArgument::Contradiction);
|
|
break;
|
|
case FunctionRegisterArgument::Yes:
|
|
Result = FCRegisterArgument(FCRegisterArgument::Yes);
|
|
break;
|
|
case FunctionRegisterArgument::No:
|
|
Result = FCRegisterArgument(FCRegisterArgument::No);
|
|
break;
|
|
default:
|
|
revng_abort();
|
|
}
|
|
|
|
// If the callee doesn't say No and the caller says yes
|
|
if (not(FunctionStatus.value() == FunctionRegisterArgument::No)
|
|
and CallerStatus.value() == FCRegisterArgument::Yes) {
|
|
// Promote caller using the Yes information
|
|
switch (FunctionStatus.value()) {
|
|
case FunctionRegisterArgument::NoOrDead:
|
|
Result = FCRegisterArgument(FCRegisterArgument::Dead);
|
|
break;
|
|
case FunctionRegisterArgument::Maybe:
|
|
Result = FCRegisterArgument(FCRegisterArgument::Yes);
|
|
break;
|
|
case FunctionRegisterArgument::Contradiction:
|
|
case FunctionRegisterArgument::Yes:
|
|
// Do nothing
|
|
break;
|
|
default:
|
|
revng_abort();
|
|
}
|
|
}
|
|
|
|
// In all other cases, no changes
|
|
|
|
// Register the result
|
|
TheCallSiteDescription.RegisterSlots[CSV].Argument = Result;
|
|
}
|
|
|
|
// Propagate the information from callers to function
|
|
FunctionRegisterArgument Result = FunctionStatus;
|
|
|
|
if (AtLeastAYes) {
|
|
switch (FunctionStatus.value()) {
|
|
case FunctionRegisterArgument::Maybe:
|
|
Result = FunctionRegisterArgument(FunctionRegisterArgument::Yes);
|
|
break;
|
|
case FunctionRegisterArgument::NoOrDead:
|
|
Result = FunctionRegisterArgument(FunctionRegisterArgument::Dead);
|
|
break;
|
|
case FunctionRegisterArgument::Contradiction:
|
|
case FunctionRegisterArgument::Yes:
|
|
case FunctionRegisterArgument::No:
|
|
// Do nothing
|
|
break;
|
|
default:
|
|
revng_abort();
|
|
}
|
|
}
|
|
|
|
// Register the result for the argument of the function
|
|
P.second.RegisterSlots[CSV].Argument = Result;
|
|
}
|
|
|
|
{
|
|
//
|
|
// Merge return values
|
|
//
|
|
|
|
// Register status at the function
|
|
const FunctionReturnValue &FunctionStatus = FRV[TheFunctionSlot];
|
|
auto Status = FunctionStatus.value();
|
|
revng_assert(Status == FunctionReturnValue::Maybe
|
|
or Status == FunctionReturnValue::No
|
|
or Status == FunctionReturnValue::YesOrDead);
|
|
|
|
// Propagate information from the function to callers (and record if at
|
|
// least on call sites says Yes or Dead)
|
|
bool AtLeastAYesOrDead = false;
|
|
for (auto &Q : FCS.CallSites) {
|
|
const CallSite &TheCallSite = Q.first;
|
|
auto &TheCallSiteDescription = *Q.second;
|
|
FunctionCallSlot FCS{ TheCallSite, Offset };
|
|
|
|
// Register status at current call site
|
|
const FunctionCallReturnValue &CallerStatus = FCRV[FCS];
|
|
auto Status = CallerStatus.value();
|
|
revng_assert(Status == FunctionCallReturnValue::Maybe
|
|
or Status == FunctionCallReturnValue::NoOrDead
|
|
or Status == FunctionCallReturnValue::Yes
|
|
or Status == FunctionCallReturnValue::Contradiction);
|
|
|
|
FunctionCallReturnValue Result = CallerStatus;
|
|
|
|
switch (FunctionStatus.value()) {
|
|
case FunctionReturnValue::No:
|
|
// No from the function is propagated as is
|
|
Result = FunctionCallReturnValue::no();
|
|
break;
|
|
case FunctionReturnValue::YesOrDead:
|
|
// Propagate the strong yes information
|
|
switch (CallerStatus.value()) {
|
|
case FunctionCallReturnValue::Maybe:
|
|
Result = FunctionCallReturnValue(FunctionCallReturnValue::Yes);
|
|
break;
|
|
case FunctionCallReturnValue::NoOrDead:
|
|
Result = FunctionCallReturnValue(FunctionCallReturnValue::Dead);
|
|
break;
|
|
case FunctionCallReturnValue::Yes:
|
|
case FunctionCallReturnValue::Contradiction:
|
|
// Do nothing
|
|
break;
|
|
default:
|
|
revng_abort();
|
|
}
|
|
break;
|
|
case FunctionReturnValue::Maybe:
|
|
break;
|
|
default:
|
|
revng_abort();
|
|
}
|
|
|
|
// In all other cases, no changes
|
|
|
|
// Record if at least one result is Yes or Dead
|
|
{
|
|
auto Status = Result.value();
|
|
AtLeastAYesOrDead = (AtLeastAYesOrDead
|
|
or Status == FunctionCallReturnValue::Yes
|
|
or Status == FunctionCallReturnValue::Dead);
|
|
}
|
|
|
|
// Register the result for this call site
|
|
TheCallSiteDescription.RegisterSlots[CSV].ReturnValue = Result;
|
|
}
|
|
|
|
// Cross-contamination of callers
|
|
if (AtLeastAYesOrDead) {
|
|
// If at least a call site states that this slot is a return value,
|
|
// all the other call sites can benefit from this information
|
|
|
|
for (auto &Q : FCS.CallSites) {
|
|
using FCReturnValue = FunctionCallReturnValue;
|
|
auto &TheCallSiteDescription = *Q.second;
|
|
auto &Value = TheCallSiteDescription.RegisterSlots[CSV].ReturnValue;
|
|
switch (Value.value()) {
|
|
case FCReturnValue::NoOrDead:
|
|
Value = FCReturnValue(FCReturnValue::Dead);
|
|
break;
|
|
case FCReturnValue::Maybe:
|
|
Value = FCReturnValue(FCReturnValue::YesOrDead);
|
|
break;
|
|
case FCReturnValue::Yes:
|
|
case FCReturnValue::Dead:
|
|
case FCReturnValue::Contradiction:
|
|
// Do nothing
|
|
break;
|
|
case FCReturnValue::No:
|
|
default:
|
|
revng_abort();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the result associated to the function
|
|
FunctionReturnValue Result = FunctionStatus;
|
|
using FCReturnValue = FunctionCallReturnValue;
|
|
|
|
if (FCS.CallSites.size() > 0) {
|
|
// At this point the information associated to the call sites is
|
|
// either all "No", one of "Yes", "Dead" and "YesOrDead" or one of
|
|
// "NoOrDead" and "Maybe"
|
|
bool AllNo = true;
|
|
bool AllYesOrDead = true;
|
|
bool AllNoOrDead = true;
|
|
|
|
// Initialize the result to propagate to the callee with the first
|
|
// call site
|
|
auto BeginIt = FCS.CallSites.begin();
|
|
auto Accumulate = BeginIt->second->RegisterSlots[CSV].ReturnValue;
|
|
|
|
for (auto &Q : FCS.CallSites) {
|
|
auto &TheCallSiteDescription = *Q.second;
|
|
auto &Value = TheCallSiteDescription.RegisterSlots[CSV].ReturnValue;
|
|
|
|
AllNo = AllNo and Value.value() == FCReturnValue::No;
|
|
|
|
auto Status = Value.value();
|
|
AllYesOrDead = (AllYesOrDead
|
|
and (Status == FCReturnValue::Yes
|
|
or Status == FCReturnValue::Dead
|
|
or Status == FCReturnValue::YesOrDead));
|
|
|
|
AllNoOrDead = (AllNoOrDead
|
|
and (Status == FCReturnValue::NoOrDead
|
|
or Status == FCReturnValue::Maybe));
|
|
|
|
// If the value has changed, move towards the most generic
|
|
if (Status != Accumulate.value()) {
|
|
using FCReturnValue = FCReturnValue;
|
|
switch (Status) {
|
|
case FCReturnValue::Yes:
|
|
case FCReturnValue::Dead:
|
|
case FCReturnValue::YesOrDead:
|
|
Accumulate = FCReturnValue(FCReturnValue::YesOrDead);
|
|
break;
|
|
case FCReturnValue::NoOrDead:
|
|
case FCReturnValue::Maybe:
|
|
Accumulate = FCReturnValue(FCReturnValue::Maybe);
|
|
break;
|
|
case FCReturnValue::No:
|
|
case FCReturnValue::Contradiction:
|
|
revng_abort();
|
|
}
|
|
}
|
|
}
|
|
|
|
// AllNo XOR AllYesOrDead XOR AllNoOrDead
|
|
revng_assert((AllNo and not(AllYesOrDead or AllNoOrDead))
|
|
or (AllYesOrDead and not(AllNo or AllNoOrDead))
|
|
or (AllNoOrDead and not(AllYesOrDead or AllNo)));
|
|
|
|
// Propagate the information from callers to function
|
|
|
|
// If AllNo, nothing to do
|
|
using FReturnValue = FunctionReturnValue;
|
|
bool IsNo = FunctionStatus.value() == FReturnValue::No;
|
|
revng_assert(AllNo ? IsNo : true);
|
|
|
|
// If the function status was maybe, we might have something to
|
|
// promote in the call
|
|
if (FunctionStatus.value() == FReturnValue::Maybe) {
|
|
switch (Accumulate.value()) {
|
|
case FCReturnValue::Yes:
|
|
case FCReturnValue::Dead:
|
|
case FCReturnValue::YesOrDead:
|
|
Result = FReturnValue(FReturnValue::YesOrDead);
|
|
break;
|
|
case FCReturnValue::NoOrDead:
|
|
Result = FReturnValue(FReturnValue::NoOrDead);
|
|
break;
|
|
case FCReturnValue::Maybe:
|
|
Result = FReturnValue(FReturnValue::Maybe);
|
|
break;
|
|
default:
|
|
revng_abort();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Register the result with the function
|
|
P.second.RegisterSlots[CSV].ReturnValue = Result;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
} // namespace StackAnalysis
|