mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
1923 lines
61 KiB
C++
1923 lines
61 KiB
C++
/// \file JumpTargetManager.cpp
|
|
/// \brief This file handles the possible jump targets encountered during
|
|
/// translation and the creation and management of the respective
|
|
/// BasicBlock.
|
|
|
|
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
//
|
|
|
|
#include <cstdint>
|
|
#include <fstream>
|
|
#include <queue>
|
|
#include <sstream>
|
|
|
|
#include "boost/icl/interval_set.hpp"
|
|
#include "boost/icl/right_open_interval.hpp"
|
|
#include "boost/type_traits/is_same.hpp"
|
|
|
|
#include "llvm/ADT/DepthFirstIterator.h"
|
|
#include "llvm/Analysis/BasicAliasAnalysis.h"
|
|
#include "llvm/Analysis/ScopedNoAliasAA.h"
|
|
#include "llvm/CodeGen/UnreachableBlockElim.h"
|
|
#include "llvm/IR/IRBuilder.h"
|
|
#include "llvm/IR/LegacyPassManager.h"
|
|
#include "llvm/IR/MDBuilder.h"
|
|
#include "llvm/IR/PassManager.h"
|
|
#include "llvm/IR/Verifier.h"
|
|
#include "llvm/Passes/PassBuilder.h"
|
|
#include "llvm/Transforms/InstCombine/InstCombine.h"
|
|
#include "llvm/Transforms/Scalar.h"
|
|
#include "llvm/Transforms/Scalar/EarlyCSE.h"
|
|
#include "llvm/Transforms/Scalar/GVN.h"
|
|
#include "llvm/Transforms/Scalar/JumpThreading.h"
|
|
#include "llvm/Transforms/Utils/Cloning.h"
|
|
#include "llvm/Transforms/Utils/Local.h"
|
|
#include "llvm/Transforms/Utils/Mem2Reg.h"
|
|
|
|
#include "revng/ADT/Queue.h"
|
|
#include "revng/BasicAnalyses/AdvancedValueInfo.h"
|
|
#include "revng/BasicAnalyses/CSVAliasAnalysis.h"
|
|
#include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h"
|
|
#include "revng/BasicAnalyses/ShrinkInstructionOperandsPass.h"
|
|
#include "revng/FunctionCallIdentification/FunctionCallIdentification.h"
|
|
#include "revng/Model/LoadModelPass.h"
|
|
#include "revng/Support/Assert.h"
|
|
#include "revng/Support/CommandLine.h"
|
|
#include "revng/Support/Debug.h"
|
|
#include "revng/Support/FunctionTags.h"
|
|
#include "revng/Support/IRHelpers.h"
|
|
#include "revng/Support/MetaAddress.h"
|
|
#include "revng/Support/Statistics.h"
|
|
#include "revng/TypeShrinking/BitLiveness.h"
|
|
#include "revng/TypeShrinking/TypeShrinking.h"
|
|
|
|
#include "AdvancedValueInfoPass.h"
|
|
#include "CPUStateAccessAnalysisPass.h"
|
|
#include "DropHelperCallsPass.h"
|
|
#include "JumpTargetManager.h"
|
|
#include "SubGraph.h"
|
|
|
|
using namespace llvm;
|
|
|
|
namespace {
|
|
|
|
Logger<> JTCountLog("jtcount");
|
|
Logger<> NewEdgesLog("new-edges");
|
|
Logger<> RegisterJTLog("registerjt");
|
|
|
|
CounterMap<std::string> HarvestingStats("harvesting");
|
|
RunningStatistics BlocksAnalyzedByAVI("blocks-analyzed-by-avi");
|
|
|
|
RegisterPass<TranslateDirectBranchesPass> X("translate-db",
|
|
"Translate Direct Branches"
|
|
" Pass",
|
|
false,
|
|
false);
|
|
|
|
} // namespace
|
|
|
|
char TranslateDirectBranchesPass::ID = 0;
|
|
|
|
void TranslateDirectBranchesPass::getAnalysisUsage(AnalysisUsage &AU) const {
|
|
AU.addRequired<DominatorTreeWrapperPass>();
|
|
AU.setPreservesAll();
|
|
}
|
|
|
|
void JumpTargetManager::assertNoUnreachable() const {
|
|
std::set<BasicBlock *> Unreachable = computeUnreachable();
|
|
if (Unreachable.size() != 0) {
|
|
VerifyLog << "The following basic blocks are unreachable:\n";
|
|
for (BasicBlock *BB : Unreachable) {
|
|
VerifyLog << " " << getName(BB) << " (predecessors:";
|
|
for (BasicBlock *Predecessor : make_range(pred_begin(BB), pred_end(BB)))
|
|
VerifyLog << " " << getName(Predecessor);
|
|
|
|
MetaAddress PC = getBasicBlockPC(BB);
|
|
if (PC.isValid()) {
|
|
auto It = JumpTargets.find(PC);
|
|
if (It != JumpTargets.end()) {
|
|
VerifyLog << ", reasons:";
|
|
for (const char *Reason : It->second.getReasonNames())
|
|
VerifyLog << " " << Reason;
|
|
}
|
|
}
|
|
|
|
VerifyLog << ")\n";
|
|
}
|
|
VerifyLog << DoLog;
|
|
revng_abort();
|
|
}
|
|
}
|
|
|
|
/// \brief Purges everything is after a call to exitTB (except the call itself)
|
|
static void exitTBCleanup(Instruction *ExitTBCall) {
|
|
BasicBlock *BB = ExitTBCall->getParent();
|
|
|
|
// Cleanup everything it's aftewards starting from the end
|
|
Instruction *ToDelete = &*(--BB->end());
|
|
while (ToDelete != ExitTBCall) {
|
|
if (auto DeadBranch = dyn_cast<BranchInst>(ToDelete))
|
|
purgeBranch(BasicBlock::iterator(DeadBranch));
|
|
else
|
|
eraseFromParent(ToDelete);
|
|
|
|
ToDelete = &*(--BB->end());
|
|
}
|
|
}
|
|
|
|
using TDBP = TranslateDirectBranchesPass;
|
|
|
|
TDBP::TranslateDirectBranchesPass(JumpTargetManager *J) :
|
|
ModulePass(ID), JTM(J), PCH(J->programCounterHandler()) {
|
|
}
|
|
|
|
using DispatcherTargets = ProgramCounterHandler::DispatcherTargets;
|
|
void TDBP::pinExitTB(CallInst *ExitTBCall, DispatcherTargets &Destinations) {
|
|
revng_assert(ExitTBCall != nullptr);
|
|
revng_assert(Destinations.size() != 0);
|
|
|
|
LLVMContext &Context = getContext(ExitTBCall);
|
|
BasicBlock *AnyPC = JTM->anyPC();
|
|
BasicBlock *UnexpectedPC = JTM->unexpectedPC();
|
|
BasicBlock *Source = ExitTBCall->getParent();
|
|
|
|
using CI = ConstantInt;
|
|
auto *ExitTBArg = CI::get(Type::getInt32Ty(Context), Destinations.size());
|
|
uint64_t OldTargetsCount = getLimitedValue(ExitTBCall->getArgOperand(0));
|
|
|
|
// TODO: we should check Destinations.size() >= OldTargetsCount
|
|
// TODO: we should also check the destinations are actually the same
|
|
|
|
BasicBlock *BB = ExitTBCall->getParent();
|
|
|
|
if (auto *Dispatcher = dyn_cast<SwitchInst>(BB->getTerminator()))
|
|
PCH->destroyDispatcher(Dispatcher);
|
|
|
|
// Kill everything is after the call to exitTB
|
|
exitTBCleanup(ExitTBCall);
|
|
|
|
// Mark this call to exitTB as handled
|
|
ExitTBCall->setArgOperand(0, ExitTBArg);
|
|
|
|
PCH->buildDispatcher(Destinations,
|
|
BB,
|
|
UnexpectedPC,
|
|
BlockType::IndirectBranchDispatcherHelperBlock);
|
|
|
|
// Move all the markers right before the branch instruction
|
|
Instruction *Last = BB->getTerminator();
|
|
auto It = ExitTBCall->getIterator();
|
|
while (isMarker(&*It)) {
|
|
// Get the marker instructions
|
|
Instruction *I = &*It;
|
|
|
|
// Move the iterator back
|
|
It--;
|
|
|
|
// Move the last moved instruction (initially the terminator)
|
|
I->moveBefore(Last);
|
|
|
|
Last = I;
|
|
}
|
|
|
|
// Notify new branches only if the amount of possible targets actually
|
|
// increased
|
|
if (Destinations.size() > OldTargetsCount)
|
|
JTM->recordNewBranches(Source, Destinations.size() - OldTargetsCount);
|
|
}
|
|
|
|
bool TDBP::pinAVIResults(Function &F) {
|
|
QuickMetadata QMD(getContext(&F));
|
|
|
|
Function *ExitTB = JTM->exitTB();
|
|
for (User *U : ExitTB->users()) {
|
|
if (auto *Call = dyn_cast<CallInst>(U)) {
|
|
if (Call->getParent()->getParent() != &F)
|
|
continue;
|
|
|
|
auto *MD = Call->getMetadata("revng.targets");
|
|
if (auto *T = dyn_cast_or_null<MDTuple>(MD)) {
|
|
if (T->getNumOperands() > 0) {
|
|
// We have at least a value, prepare a list of jump targets
|
|
ProgramCounterHandler::DispatcherTargets Values;
|
|
Values.reserve(T->getNumOperands());
|
|
for (const MDOperand &Operand : T->operands()) {
|
|
auto *CMA = QMD.extract<Constant *>(Operand.get());
|
|
auto MA = MetaAddress::fromConstant(CMA);
|
|
Values.emplace_back(MA, JTM->getBlockAt(MA));
|
|
}
|
|
|
|
pinExitTB(Call, Values);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void TDBP::pinConstantStoreInternal(MetaAddress Address, CallInst *ExitTBCall) {
|
|
revng_assert(Address.isValid());
|
|
|
|
BasicBlock *TargetBlock = JTM->registerJT(Address, JTReason::DirectJump);
|
|
|
|
const Module *M = getModule(ExitTBCall);
|
|
LLVMContext &Context = getContext(M);
|
|
|
|
// Remove unreachable right after the exit_tb
|
|
BasicBlock::iterator CallIt(ExitTBCall);
|
|
BasicBlock::iterator BlockEnd = ExitTBCall->getParent()->end();
|
|
CallIt++;
|
|
revng_assert(CallIt != BlockEnd and isa<UnreachableInst>(&*CallIt));
|
|
eraseFromParent(&*CallIt);
|
|
|
|
// Cleanup of what's afterwards (only a unconditional jump is
|
|
// allowed)
|
|
CallIt = BasicBlock::iterator(ExitTBCall);
|
|
BlockEnd = ExitTBCall->getParent()->end();
|
|
if (++CallIt != BlockEnd)
|
|
purgeBranch(CallIt);
|
|
|
|
if (TargetBlock != nullptr) {
|
|
// A target was found, jump there
|
|
BranchInst::Create(TargetBlock, ExitTBCall->getParent());
|
|
JTM->recordNewBranches(ExitTBCall->getParent(), 1);
|
|
} else {
|
|
// We're jumping to an unknown or invalid location,
|
|
// jump back to the dispatcher
|
|
// TODO: emit a warning
|
|
BranchInst::Create(JTM->unexpectedPC(), ExitTBCall);
|
|
}
|
|
|
|
eraseFromParent(ExitTBCall);
|
|
}
|
|
|
|
bool TDBP::pinConstantStore(Function &F) {
|
|
auto ExitTB = JTM->exitTB();
|
|
auto ExitTBIt = ExitTB->use_begin();
|
|
while (ExitTBIt != ExitTB->use_end()) {
|
|
// Take note of the use and increment the iterator immediately: this allows
|
|
// us to erase the call to exit_tb without unexpected behaviors
|
|
Use &ExitTBUse = *ExitTBIt++;
|
|
auto *Call = cast<CallInst>(ExitTBUse.getUser());
|
|
revng_assert(Call->getCalledFunction() == ExitTB);
|
|
|
|
// Look for the last write to the PC
|
|
auto [Result, NextPC] = PCH->getUniqueJumpTarget(Call->getParent());
|
|
|
|
switch (Result) {
|
|
case NextJumpTarget::Unique:
|
|
// A constant store was born
|
|
revng_assert(NextPC.isValid());
|
|
pinConstantStoreInternal(NextPC, Call);
|
|
break;
|
|
|
|
case NextJumpTarget::Multiple:
|
|
// Nothing to do, it's an indirect jump
|
|
break;
|
|
|
|
case NextJumpTarget::Helper:
|
|
forceFallthroughAfterHelper(Call);
|
|
break;
|
|
|
|
default:
|
|
revng_abort();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TDBP::forceFallthroughAfterHelper(CallInst *Call) {
|
|
// If someone else already took care of the situation, quit
|
|
if (getLimitedValue(Call->getArgOperand(0)) > 0)
|
|
return false;
|
|
|
|
bool ForceFallthrough = false;
|
|
|
|
BasicBlock::reverse_iterator It(++Call->getReverseIterator());
|
|
auto *BB = Call->getParent();
|
|
auto EndIt = BB->rend();
|
|
while (!ForceFallthrough) {
|
|
while (It != EndIt) {
|
|
Instruction *I = &*It;
|
|
if (auto *Store = dyn_cast<StoreInst>(I)) {
|
|
if (PCH->affectsPC(Store)) {
|
|
// We found a PC-store, give up
|
|
return false;
|
|
}
|
|
} else if (isCallToHelper(I)) {
|
|
// We found a call to an helper
|
|
ForceFallthrough = true;
|
|
break;
|
|
}
|
|
It++;
|
|
}
|
|
|
|
if (!ForceFallthrough) {
|
|
// Proceed only to unique predecessor, if present
|
|
if (auto *Pred = BB->getUniquePredecessor()) {
|
|
BB = Pred;
|
|
It = BB->rbegin();
|
|
EndIt = BB->rend();
|
|
} else {
|
|
// We have multiple predecessors, give up
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
exitTBCleanup(Call);
|
|
|
|
IRBuilder<> Builder(Call->getParent());
|
|
Call->setArgOperand(0, Builder.getInt32(1));
|
|
|
|
// Create the fallthrough jump
|
|
MetaAddress NextPC = JTM->getNextPC(Call);
|
|
|
|
// Get the fallthrough basic block and emit a conditional branch, if not
|
|
// possible simply jump to anyPC
|
|
BasicBlock *AnyPC = JTM->anyPC();
|
|
if (BasicBlock *NextPCBB = JTM->registerJT(NextPC, JTReason::PostHelper)) {
|
|
PCH->buildHotPath(Builder, { NextPC, NextPCBB }, AnyPC);
|
|
} else {
|
|
Builder.CreateBr(AnyPC);
|
|
}
|
|
|
|
JTM->recordNewBranches(Call->getParent(), 1);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TDBP::runOnModule(Module &M) {
|
|
Function &F = *M.getFunction("root");
|
|
pinConstantStore(F);
|
|
pinAVIResults(F);
|
|
return true;
|
|
}
|
|
|
|
MaterializedValue
|
|
JumpTargetManager::readFromPointer(Constant *Pointer, bool IsLittleEndian) {
|
|
Type *LoadedType = Pointer->getType()->getPointerElementType();
|
|
const DataLayout &DL = TheModule.getDataLayout();
|
|
unsigned LoadSize = DL.getTypeSizeInBits(LoadedType) / 8;
|
|
auto NewAPInt = [LoadSize](uint64_t V) { return APInt(LoadSize * 8, V); };
|
|
|
|
Value *RealPointer = skipCasts(Pointer);
|
|
uint64_t RawLoadAddress = 0;
|
|
if (not isa<ConstantPointerNull>(RealPointer)) {
|
|
RawLoadAddress = getZExtValue(cast<ConstantInt>(RealPointer), DL);
|
|
}
|
|
auto LoadAddress = fromGeneric(RawLoadAddress);
|
|
|
|
UnusedCodePointers.erase(LoadAddress);
|
|
registerReadRange(LoadAddress, LoadSize);
|
|
|
|
// Prevent overflow when computing the label interval
|
|
if ((LoadAddress + LoadSize).addressLowerThan(LoadAddress)) {
|
|
return MaterializedValue::invalid();
|
|
}
|
|
|
|
//
|
|
// Check relocations
|
|
//
|
|
unsigned MatchCount = 0;
|
|
MaterializedValue Result;
|
|
|
|
// Check dynamic functions-related relocations
|
|
for (const model::DynamicFunction &Function :
|
|
Model->ImportedDynamicFunctions) {
|
|
for (const model::Relocation &Relocation : Function.Relocations) {
|
|
uint64_t Addend = Relocation.Addend;
|
|
auto RelocationSize = model::RelocationType::getSize(Relocation.Type);
|
|
if (LoadAddress == Relocation.Address and LoadSize == RelocationSize) {
|
|
revng_assert(not StringRef(Function.name()).contains('\0'));
|
|
Result = { Function.OriginalName, NewAPInt(Addend) };
|
|
++MatchCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check segment-related relocations
|
|
for (const model::Segment &Segment : Model->Segments) {
|
|
for (const model::Relocation &Relocation : Segment.Relocations) {
|
|
uint64_t Addend = Relocation.Addend;
|
|
auto RelocationSize = model::RelocationType::getSize(Relocation.Type);
|
|
if (LoadAddress == Relocation.Address and LoadSize == RelocationSize) {
|
|
MetaAddress Address = Segment.StartAddress + Addend;
|
|
if (Address.isValid()) {
|
|
Result = { NewAPInt(Address.address()) };
|
|
++MatchCount;
|
|
} else {
|
|
// TODO: log message
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MatchCount == 1) {
|
|
return Result;
|
|
} else if (MatchCount > 1) {
|
|
// TODO: log message
|
|
}
|
|
|
|
// No labels found, fall back to read the raw value, if available
|
|
auto MaybeValue = BinaryView.readInteger(LoadAddress,
|
|
LoadSize,
|
|
IsLittleEndian);
|
|
|
|
if (MaybeValue)
|
|
return { NewAPInt(*MaybeValue) };
|
|
else
|
|
return {};
|
|
}
|
|
|
|
JumpTargetManager::JumpTargetManager(Function *TheFunction,
|
|
ProgramCounterHandler *PCH,
|
|
CSAAFactory CreateCSAA,
|
|
const TupleTree<model::Binary> &Model,
|
|
const RawBinaryView &BinaryView) :
|
|
TheModule(*TheFunction->getParent()),
|
|
Context(TheModule.getContext()),
|
|
TheFunction(TheFunction),
|
|
OriginalInstructionAddresses(),
|
|
JumpTargets(),
|
|
ExitTB(nullptr),
|
|
Dispatcher(nullptr),
|
|
DispatcherSwitch(nullptr),
|
|
CurrentCFGForm(CFGForm::UnknownForm),
|
|
CreateCSAA(CreateCSAA),
|
|
PCH(PCH),
|
|
Model(Model),
|
|
BinaryView(BinaryView) {
|
|
|
|
FunctionType *ExitTBTy = FunctionType::get(Type::getVoidTy(Context),
|
|
{ Type::getInt32Ty(Context) },
|
|
false);
|
|
FunctionCallee ExitCallee = TheModule.getOrInsertFunction("exitTB", ExitTBTy);
|
|
ExitTB = cast<Function>(ExitCallee.getCallee());
|
|
FunctionTags::Marker.addTo(ExitTB);
|
|
|
|
prepareDispatcher();
|
|
|
|
//
|
|
// Collect executable ranges from the model
|
|
//
|
|
for (const model::Segment &Segment : Model->Segments) {
|
|
if (Segment.IsExecutable) {
|
|
if (Segment.Sections.size() > 0) {
|
|
for (const model::Section &Section : Segment.Sections) {
|
|
if (Section.ContainsCode) {
|
|
ExecutableRanges.emplace_back(Section.StartAddress,
|
|
Section.endAddress());
|
|
}
|
|
}
|
|
} else {
|
|
ExecutableRanges.emplace_back(Segment.StartAddress,
|
|
Segment.endAddress());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Configure GlobalValueNumbering
|
|
StringMap<cl::Option *> &Options(cl::getRegisteredOptions());
|
|
getOption<bool>(Options, "enable-load-pre")->setInitialValue(false);
|
|
getOption<unsigned>(Options, "memdep-block-scan-limit")->setInitialValue(100);
|
|
// getOption<bool>(Options, "enable-pre")->setInitialValue(false);
|
|
// getOption<uint32_t>(Options, "max-recurse-depth")->setInitialValue(10);
|
|
}
|
|
|
|
void JumpTargetManager::harvestGlobalData() {
|
|
// Register symbols
|
|
for (const model::Function &Function : Model->Functions)
|
|
registerJT(Function.Entry, JTReason::FunctionSymbol);
|
|
|
|
// Register ExtraCodeAddresses
|
|
for (MetaAddress Address : Model->ExtraCodeAddresses)
|
|
registerJT(Address, JTReason::GlobalData);
|
|
|
|
for (auto &[Segment, Data] : BinaryView.segments()) {
|
|
MetaAddress StartVirtualAddress = Segment.StartAddress;
|
|
const unsigned char *DataStart = Data.begin();
|
|
const unsigned char *DataEnd = Data.end();
|
|
|
|
using namespace model::Architecture;
|
|
bool IsLittleEndian = isLittleEndian(Model->Architecture);
|
|
auto PointerSize = getPointerSize(Model->Architecture);
|
|
using endianness = support::endianness;
|
|
if (PointerSize == 8) {
|
|
if (IsLittleEndian)
|
|
findCodePointers<uint64_t, endianness::little>(StartVirtualAddress,
|
|
DataStart,
|
|
DataEnd);
|
|
else
|
|
findCodePointers<uint64_t, endianness::big>(StartVirtualAddress,
|
|
DataStart,
|
|
DataEnd);
|
|
} else if (PointerSize == 4) {
|
|
if (IsLittleEndian)
|
|
findCodePointers<uint32_t, endianness::little>(StartVirtualAddress,
|
|
DataStart,
|
|
DataEnd);
|
|
else
|
|
findCodePointers<uint32_t, endianness::big>(StartVirtualAddress,
|
|
DataStart,
|
|
DataEnd);
|
|
}
|
|
}
|
|
|
|
revng_log(JTCountLog,
|
|
"JumpTargets found in global data: " << std::dec
|
|
<< Unexplored.size());
|
|
}
|
|
|
|
template<typename value_type, unsigned endian>
|
|
void JumpTargetManager::findCodePointers(MetaAddress StartVirtualAddress,
|
|
const unsigned char *Start,
|
|
const unsigned char *End) {
|
|
using support::endianness;
|
|
using support::endian::read;
|
|
for (auto Pos = Start; Pos < End - sizeof(value_type); Pos++) {
|
|
auto Read = read<value_type, static_cast<endianness>(endian), 1>;
|
|
uint64_t RawValue = Read(Pos);
|
|
MetaAddress Value = fromPC(RawValue);
|
|
if (Value.isInvalid())
|
|
continue;
|
|
|
|
BasicBlock *Result = registerJT(Value, JTReason::GlobalData);
|
|
|
|
if (Result != nullptr)
|
|
UnusedCodePointers.insert(StartVirtualAddress + (Pos - Start));
|
|
}
|
|
}
|
|
|
|
/// Handle a new program counter. We might already have a basic block for that
|
|
/// program counter, or we could even have a translation for it. Return one of
|
|
/// these, if appropriate.
|
|
///
|
|
/// \param PC the new program counter.
|
|
/// \param ShouldContinue an out parameter indicating whether the returned
|
|
/// basic block was just a placeholder or actually contains a
|
|
/// translation.
|
|
///
|
|
/// \return the basic block to use from now on, or null if the program counter
|
|
/// is not associated to a basic block.
|
|
// TODO: make this return a pair
|
|
BasicBlock *JumpTargetManager::newPC(MetaAddress PC, bool &ShouldContinue) {
|
|
revng_assert(PC.isValid());
|
|
|
|
// Did we already meet this PC?
|
|
auto JTIt = JumpTargets.find(PC);
|
|
if (JTIt != JumpTargets.end()) {
|
|
// If it was planned to explore it in the future, just to do it now
|
|
for (auto UnexploredIt = Unexplored.begin();
|
|
UnexploredIt != Unexplored.end();
|
|
UnexploredIt++) {
|
|
|
|
if (UnexploredIt->first == PC) {
|
|
BasicBlock *Result = UnexploredIt->second;
|
|
|
|
// Check if we already have a translation for that
|
|
ShouldContinue = Result->empty();
|
|
if (ShouldContinue) {
|
|
// We don't, OK let's explore it next
|
|
Unexplored.erase(UnexploredIt);
|
|
} else {
|
|
// We do, it will be purged at the next `peek`
|
|
revng_assert(ToPurge.count(Result) != 0);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
// It wasn't planned to visit it, so we've already been there, just jump
|
|
// there
|
|
BasicBlock *BB = JTIt->second.head();
|
|
revng_assert(!BB->empty());
|
|
ShouldContinue = false;
|
|
return BB;
|
|
}
|
|
|
|
// Check if we already translated this PC even if it's not associated to a
|
|
// basic block (i.e., we have to split its basic block). This typically
|
|
// happens with variable-length instruction encodings.
|
|
if (OriginalInstructionAddresses.count(PC) != 0) {
|
|
ShouldContinue = false;
|
|
return registerJT(PC, JTReason::AmbigousInstruction);
|
|
}
|
|
|
|
// We don't know anything about this PC
|
|
return nullptr;
|
|
}
|
|
|
|
/// Save the PC-Instruction association for future use (jump target)
|
|
void JumpTargetManager::registerInstruction(MetaAddress PC,
|
|
Instruction *Instruction) {
|
|
revng_assert(PC.isValid());
|
|
|
|
// Never save twice a PC
|
|
revng_assert(!OriginalInstructionAddresses.count(PC));
|
|
OriginalInstructionAddresses[PC] = Instruction;
|
|
}
|
|
|
|
// TODO: this is a candidate for BFSVisit
|
|
std::pair<MetaAddress, uint64_t>
|
|
JumpTargetManager::getPC(Instruction *TheInstruction) const {
|
|
CallInst *NewPCCall = nullptr;
|
|
std::set<BasicBlock *> Visited;
|
|
std::queue<BasicBlock::reverse_iterator> WorkList;
|
|
if (TheInstruction->getIterator() == TheInstruction->getParent()->begin())
|
|
WorkList.push(--TheInstruction->getParent()->rend());
|
|
else
|
|
WorkList.push(++TheInstruction->getReverseIterator());
|
|
|
|
while (!WorkList.empty()) {
|
|
auto I = WorkList.front();
|
|
WorkList.pop();
|
|
auto *BB = I->getParent();
|
|
auto End = BB->rend();
|
|
|
|
// Go through the instructions looking for calls to newpc
|
|
for (; I != End; I++) {
|
|
if (auto Marker = dyn_cast<CallInst>(&*I)) {
|
|
// TODO: comparing strings is not very elegant
|
|
auto *Callee = Marker->getCalledFunction();
|
|
if (Callee != nullptr && Callee->getName() == "newpc") {
|
|
|
|
// We found two distinct newpc leading to the requested instruction
|
|
if (NewPCCall != nullptr)
|
|
return { MetaAddress::invalid(), 0 };
|
|
|
|
NewPCCall = Marker;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we haven't find a newpc call yet, continue exploration backward
|
|
if (NewPCCall == nullptr) {
|
|
// If one of the predecessors is the dispatcher, don't explore any further
|
|
for (BasicBlock *Predecessor : predecessors(BB)) {
|
|
// Assert we didn't reach the almighty dispatcher
|
|
revng_assert(not(isPartOfRootDispatcher(Predecessor)));
|
|
}
|
|
|
|
for (BasicBlock *Predecessor : predecessors(BB)) {
|
|
// Ignore already visited or empty BBs
|
|
if (!Predecessor->empty()
|
|
&& Visited.find(Predecessor) == Visited.end()) {
|
|
WorkList.push(Predecessor->rbegin());
|
|
Visited.insert(Predecessor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Couldn't find the current PC
|
|
if (NewPCCall == nullptr)
|
|
return { MetaAddress::invalid(), 0 };
|
|
|
|
auto PC = MetaAddress::fromConstant(NewPCCall->getArgOperand(0));
|
|
uint64_t Size = getLimitedValue(NewPCCall->getArgOperand(1));
|
|
revng_assert(Size != 0);
|
|
return { PC, Size };
|
|
}
|
|
|
|
/// \brief Class to iterate over all the BBs associated to a translated PC
|
|
class BasicBlockVisitor {
|
|
public:
|
|
BasicBlockVisitor(const SwitchInst *Dispatcher) :
|
|
Dispatcher(Dispatcher),
|
|
JumpTargetIndex(0),
|
|
JumpTargetsCount(Dispatcher->getNumSuccessors()),
|
|
DL(Dispatcher->getParent()->getParent()->getParent()->getDataLayout()) {}
|
|
|
|
void enqueue(BasicBlock *BB) {
|
|
if (Visited.count(BB))
|
|
return;
|
|
Visited.insert(BB);
|
|
|
|
MetaAddress PC = getPC(BB);
|
|
if (PC.isInvalid())
|
|
SamePC.push(BB);
|
|
else
|
|
NewPC.push({ BB, PC });
|
|
}
|
|
|
|
std::pair<BasicBlock *, MetaAddress> pop() {
|
|
if (!SamePC.empty()) {
|
|
auto Result = SamePC.front();
|
|
SamePC.pop();
|
|
return { Result, MetaAddress::invalid() };
|
|
} else if (!NewPC.empty()) {
|
|
auto Result = NewPC.front();
|
|
NewPC.pop();
|
|
return Result;
|
|
} else if (JumpTargetIndex < JumpTargetsCount) {
|
|
BasicBlock *BB = Dispatcher->getSuccessor(JumpTargetIndex);
|
|
JumpTargetIndex++;
|
|
return { BB, getPC(BB) };
|
|
} else {
|
|
return { nullptr, MetaAddress::invalid() };
|
|
}
|
|
}
|
|
|
|
private:
|
|
MetaAddress getPC(BasicBlock *BB) {
|
|
if (!BB->empty()) {
|
|
if (auto *Call = dyn_cast<CallInst>(&*BB->begin())) {
|
|
Function *Callee = Call->getCalledFunction();
|
|
// TODO: comparing with "newpc" string is sad
|
|
if (Callee != nullptr && Callee->getName() == "newpc") {
|
|
return MetaAddress::fromConstant(Call->getArgOperand(0));
|
|
}
|
|
}
|
|
}
|
|
|
|
return MetaAddress::invalid();
|
|
}
|
|
|
|
private:
|
|
const SwitchInst *Dispatcher;
|
|
unsigned JumpTargetIndex;
|
|
unsigned JumpTargetsCount;
|
|
const DataLayout &DL;
|
|
std::set<BasicBlock *> Visited;
|
|
std::queue<BasicBlock *> SamePC;
|
|
std::queue<std::pair<BasicBlock *, MetaAddress>> NewPC;
|
|
};
|
|
|
|
void JumpTargetManager::fixPostHelperPC() {
|
|
for (BasicBlock &BB : *TheFunction) {
|
|
for (Instruction &I : BB) {
|
|
if (auto *Call = getCallToHelper(&I)) {
|
|
auto Written = std::move(getCSVUsedByHelperCall(Call).Written);
|
|
auto WritesPC = [this](GlobalVariable *CSV) {
|
|
return PCH->affectsPC(CSV);
|
|
};
|
|
auto End = Written.end();
|
|
if (std::find_if(Written.begin(), End, WritesPC) != End) {
|
|
IRBuilder<> Builder(Call->getParent(), ++Call->getIterator());
|
|
PCH->deserializePC(Builder);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void JumpTargetManager::translateIndirectJumps() {
|
|
if (ExitTB->use_empty())
|
|
return;
|
|
|
|
auto I = ExitTB->use_begin();
|
|
while (I != ExitTB->use_end()) {
|
|
Use &ExitTBUse = *I++;
|
|
if (auto *Call = dyn_cast<CallInst>(ExitTBUse.getUser())) {
|
|
if (Call->getCalledFunction() == ExitTB) {
|
|
|
|
// Look for the last write to the PC
|
|
BasicBlock *CallBB = Call->getParent();
|
|
auto [Result, NextPC] = PCH->getUniqueJumpTarget(CallBB);
|
|
revng_check(Result != NextJumpTarget::Unique
|
|
and "Direct jumps should not be handled here");
|
|
|
|
if (getLimitedValue(Call->getArgOperand(0)) == 0) {
|
|
exitTBCleanup(Call);
|
|
BranchInst::Create(AnyPC, Call);
|
|
}
|
|
|
|
eraseFromParent(Call);
|
|
}
|
|
}
|
|
}
|
|
|
|
revng_assert(ExitTB->use_empty());
|
|
eraseFromParent(ExitTB);
|
|
ExitTB = nullptr;
|
|
}
|
|
|
|
JumpTargetManager::BlockWithAddress JumpTargetManager::peek() {
|
|
// If we just harvested new branches, keep exploring
|
|
do {
|
|
harvest();
|
|
} while (Unexplored.empty() and NewBranches != 0);
|
|
|
|
// Purge all the partial translations we know might be wrong
|
|
for (BasicBlock *BB : ToPurge)
|
|
purgeTranslation(BB);
|
|
ToPurge.clear();
|
|
|
|
if (Unexplored.empty()) {
|
|
revng_log(JTCountLog, "We're done looking for jump targets");
|
|
return NoMoreTargets;
|
|
} else {
|
|
BlockWithAddress Result = Unexplored.back();
|
|
Unexplored.pop_back();
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
BasicBlock *JumpTargetManager::getBlockAt(MetaAddress PC) {
|
|
revng_assert(PC.isValid());
|
|
|
|
auto TargetIt = JumpTargets.find(PC);
|
|
revng_assert(TargetIt != JumpTargets.end());
|
|
return TargetIt->second.head();
|
|
}
|
|
|
|
/// \brief Check if among \p BB's predecessors there's \p Target
|
|
inline bool hasRootDispatcherPredecessor(llvm::BasicBlock *BB) {
|
|
for (llvm::BasicBlock *Predecessor : predecessors(BB))
|
|
if (isPartOfRootDispatcher(Predecessor))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void JumpTargetManager::purgeTranslation(BasicBlock *Start) {
|
|
OnceQueue<BasicBlock *> Queue;
|
|
Queue.insert(Start);
|
|
|
|
// Collect all the descendants, except if we meet a jump target
|
|
while (!Queue.empty()) {
|
|
BasicBlock *BB = Queue.pop();
|
|
Instruction *Terminator = BB->getTerminator();
|
|
for (BasicBlock *Successor : successors(Terminator)) {
|
|
if (isTranslatedBB(Successor) and not isJumpTarget(Successor)
|
|
and not hasRootDispatcherPredecessor(Successor)) {
|
|
Queue.insert(Successor);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Erase all the visited basic blocks
|
|
std::set<BasicBlock *> Visited = Queue.visited();
|
|
|
|
// Build a subgraph, so that we can visit it in post order, and purge the
|
|
// content of each basic block
|
|
SubGraph<BasicBlock *> TranslatedBBs(Start, Visited);
|
|
for (auto *Node : post_order(TranslatedBBs)) {
|
|
BasicBlock *BB = Node->get();
|
|
while (!BB->empty()) {
|
|
Instruction *I = &*(--BB->end());
|
|
|
|
if (CallInst *Call = getCallTo(I, "newpc")) {
|
|
auto *Address = Call->getArgOperand(0);
|
|
OriginalInstructionAddresses.erase(MetaAddress::fromConstant(Address));
|
|
}
|
|
eraseInstruction(I);
|
|
}
|
|
}
|
|
|
|
// Remove Start, since we want to keep it (even if empty)
|
|
Visited.erase(Start);
|
|
|
|
for (BasicBlock *BB : Visited) {
|
|
// We might have some predecessorless basic blocks jumping to us, purge them
|
|
// TODO: why this?
|
|
while (pred_begin(BB) != pred_end(BB)) {
|
|
BasicBlock *Predecessor = *pred_begin(BB);
|
|
revng_assert(pred_empty(Predecessor));
|
|
eraseFromParent(Predecessor);
|
|
}
|
|
|
|
revng_assert(BB->use_empty());
|
|
eraseFromParent(BB);
|
|
}
|
|
}
|
|
|
|
// TODO: register Reason
|
|
BasicBlock *
|
|
JumpTargetManager::registerJT(MetaAddress PC, JTReason::Values Reason) {
|
|
revng_check(PC.isValid());
|
|
|
|
if (not isPC(PC))
|
|
return nullptr;
|
|
|
|
revng_log(RegisterJTLog,
|
|
"Registering bb." << nameForAddress(PC) << " for "
|
|
<< JTReason::getName(Reason));
|
|
|
|
// Do we already have a BasicBlock for this PC?
|
|
BlockMap::iterator TargetIt = JumpTargets.find(PC);
|
|
if (TargetIt != JumpTargets.end()) {
|
|
// Case 1: there's already a BasicBlock for that address, return it
|
|
BasicBlock *BB = TargetIt->second.head();
|
|
TargetIt->second.setReason(Reason);
|
|
return BB;
|
|
}
|
|
|
|
// Did we already meet this PC (i.e. do we know what's the associated
|
|
// instruction)?
|
|
BasicBlock *NewBlock = nullptr;
|
|
InstructionMap::iterator InstrIt = OriginalInstructionAddresses.find(PC);
|
|
if (InstrIt != OriginalInstructionAddresses.end()) {
|
|
// Case 2: the address has already been met, but needs to be promoted to
|
|
// BasicBlock level.
|
|
Instruction *I = InstrIt->second;
|
|
BasicBlock *ContainingBlock = I->getParent();
|
|
if (isFirst(I)) {
|
|
NewBlock = ContainingBlock;
|
|
} else {
|
|
revng_assert(I != nullptr && I->getIterator() != ContainingBlock->end());
|
|
NewBlock = ContainingBlock->splitBasicBlock(I);
|
|
}
|
|
|
|
// Register the basic block and all of its descendants to be purged so that
|
|
// we can retranslate this PC
|
|
// TODO: this might create a problem if QEMU generates control flow that
|
|
// crosses an instruction boundary
|
|
ToPurge.insert(NewBlock);
|
|
|
|
} else {
|
|
// Case 3: the address has never been met, create a temporary one, register
|
|
// it for future exploration and return it
|
|
NewBlock = BasicBlock::Create(Context, "", TheFunction);
|
|
}
|
|
|
|
Unexplored.push_back(BlockWithAddress(PC, NewBlock));
|
|
|
|
std::stringstream Name;
|
|
Name << "bb." << nameForAddress(PC);
|
|
NewBlock->setName(Name.str());
|
|
|
|
// Create a case for the address associated to the new block, if the
|
|
// dispatcher has alredy been emitted
|
|
if (DispatcherSwitch != nullptr) {
|
|
PCH->addCaseToDispatcher(DispatcherSwitch,
|
|
{ PC, NewBlock },
|
|
BlockType::RootDispatcherHelperBlock);
|
|
}
|
|
|
|
// Associate the PC with the chosen basic block
|
|
JumpTargets[PC] = JumpTarget(NewBlock, Reason);
|
|
|
|
// PC was not a jump target, record it as new
|
|
AVIPCWhiteList.insert(PC);
|
|
|
|
return NewBlock;
|
|
}
|
|
|
|
void JumpTargetManager::registerReadRange(MetaAddress Address, uint64_t Size) {
|
|
using interval = boost::icl::interval<MetaAddress, CompareAddress>;
|
|
ReadIntervalSet += interval::right_open(Address, Address + Size);
|
|
}
|
|
|
|
void JumpTargetManager::prepareDispatcher() {
|
|
IRBuilder<> Builder(Context);
|
|
QuickMetadata QMD(Context);
|
|
|
|
// Create the first block of the dispatcher
|
|
BasicBlock *Entry = BasicBlock::Create(Context,
|
|
"dispatcher.entry",
|
|
TheFunction);
|
|
|
|
// The default case of the switch statement it's an unhandled cases
|
|
DispatcherFail = BasicBlock::Create(Context,
|
|
"dispatcher.default",
|
|
TheFunction);
|
|
Builder.SetInsertPoint(DispatcherFail);
|
|
|
|
auto *UnknownPCTy = FunctionType::get(Type::getVoidTy(Context),
|
|
{ MetaAddress::getStruct(&TheModule) },
|
|
false);
|
|
FunctionCallee UnknownPC = TheModule.getOrInsertFunction("unknownPC",
|
|
UnknownPCTy);
|
|
{
|
|
auto *UnknownPCFunction = cast<Function>(skipCasts(UnknownPC.getCallee()));
|
|
FunctionTags::Exceptional.addTo(UnknownPCFunction);
|
|
}
|
|
|
|
Builder.CreateCall(UnknownPC, PCH->loadPC(Builder));
|
|
auto *FailUnreachable = Builder.CreateUnreachable();
|
|
setBlockType(FailUnreachable, BlockType::DispatcherFailureBlock);
|
|
|
|
Dispatcher = Entry;
|
|
|
|
// Create basic blocks to handle jumps to any PC and to a PC we didn't expect
|
|
AnyPC = BasicBlock::Create(Context, "anypc", TheFunction);
|
|
UnexpectedPC = BasicBlock::Create(Context, "unexpectedpc", TheFunction);
|
|
}
|
|
|
|
static void purge(BasicBlock *BB) {
|
|
// Allow up to a single instruction in the basic block
|
|
if (!BB->empty())
|
|
eraseFromParent(&*BB->begin());
|
|
revng_assert(BB->empty());
|
|
}
|
|
|
|
std::set<BasicBlock *> JumpTargetManager::computeUnreachable() const {
|
|
ReversePostOrderTraversal<BasicBlock *> RPOT(&TheFunction->getEntryBlock());
|
|
std::set<BasicBlock *> Reachable;
|
|
for (BasicBlock *BB : RPOT)
|
|
Reachable.insert(BB);
|
|
|
|
// TODO: why is isTranslatedBB(&BB) necessary?
|
|
std::set<BasicBlock *> Unreachable;
|
|
for (BasicBlock &BB : *TheFunction)
|
|
if (Reachable.count(&BB) == 0 and isTranslatedBB(&BB))
|
|
Unreachable.insert(&BB);
|
|
|
|
return Unreachable;
|
|
}
|
|
|
|
void JumpTargetManager::setCFGForm(CFGForm::Values NewForm,
|
|
MetaAddressSet *JumpTargetsWhitelist) {
|
|
revng_assert(CurrentCFGForm != NewForm);
|
|
revng_assert(NewForm != CFGForm::UnknownForm);
|
|
|
|
std::set<BasicBlock *> Unreachable;
|
|
static bool First = true;
|
|
if (not First and VerifyLog.isEnabled()) {
|
|
assertNoUnreachable();
|
|
}
|
|
First = false;
|
|
|
|
CFGForm::Values OldForm = CurrentCFGForm;
|
|
CurrentCFGForm = NewForm;
|
|
|
|
//
|
|
// Recreate AnyPC and UnexpectedPC
|
|
//
|
|
switch (NewForm) {
|
|
case CFGForm::SemanticPreserving:
|
|
purge(AnyPC);
|
|
BranchInst::Create(dispatcher(), AnyPC);
|
|
// TODO: Here we should have an hard fail, since it's the situation in
|
|
// which we expected to know where execution could go but we made a
|
|
// mistake.
|
|
purge(UnexpectedPC);
|
|
BranchInst::Create(dispatcher(), UnexpectedPC);
|
|
break;
|
|
|
|
case CFGForm::RecoveredOnly:
|
|
case CFGForm::NoFunctionCalls:
|
|
purge(AnyPC);
|
|
new UnreachableInst(Context, AnyPC);
|
|
purge(UnexpectedPC);
|
|
new UnreachableInst(Context, UnexpectedPC);
|
|
break;
|
|
|
|
default:
|
|
revng_abort("Not implemented yet");
|
|
}
|
|
|
|
setBlockType(AnyPC->getTerminator(), BlockType::AnyPCBlock);
|
|
setBlockType(UnexpectedPC->getTerminator(), BlockType::UnexpectedPCBlock);
|
|
|
|
// Adjust successors of jump instructions tagged as function calls:
|
|
//
|
|
// * NoFunctionsCallsCFG: the successor is the fallthrough
|
|
// * otherwise: the successor is the callee
|
|
if (NewForm == CFGForm::NoFunctionCalls
|
|
|| OldForm == CFGForm::NoFunctionCalls) {
|
|
if (auto *FunctionCall = TheModule.getFunction("function_call")) {
|
|
for (User *U : FunctionCall->users()) {
|
|
auto *Call = cast<CallInst>(U);
|
|
|
|
// Ignore indirect calls
|
|
// TODO: why this is needed is unclear
|
|
if (isa<ConstantPointerNull>(Call->getArgOperand(0)))
|
|
continue;
|
|
|
|
Instruction *Terminator = nextNonMarker(Call);
|
|
revng_assert(Terminator->getNumSuccessors() == 1);
|
|
|
|
// Get the correct argument, the first is the callee, the second the
|
|
// return basic block
|
|
int OperandIndex = NewForm == CFGForm::NoFunctionCalls ? 1 : 0;
|
|
Value *Op = Call->getArgOperand(OperandIndex);
|
|
BasicBlock *NewSuccessor = cast<BlockAddress>(Op)->getBasicBlock();
|
|
Terminator->setSuccessor(0, NewSuccessor);
|
|
}
|
|
}
|
|
}
|
|
|
|
rebuildDispatcher(JumpTargetsWhitelist);
|
|
|
|
if (VerifyLog.isEnabled()) {
|
|
assertNoUnreachable();
|
|
}
|
|
}
|
|
|
|
void JumpTargetManager::rebuildDispatcher(MetaAddressSet *Whitelist) {
|
|
|
|
if (DispatcherSwitch != nullptr) {
|
|
revng_assert(DispatcherSwitch->getParent() == Dispatcher);
|
|
|
|
// Purge the old dispatcher
|
|
PCH->destroyDispatcher(DispatcherSwitch);
|
|
}
|
|
|
|
ProgramCounterHandler::DispatcherTargets Targets;
|
|
|
|
// Add all the (whitelisted) jump targets if we're using the
|
|
// SemanticPreserving, or only those with no predecessors.
|
|
bool IsWhitelistActive = (Whitelist != nullptr);
|
|
for (auto &[PC, JumpTarget] : JumpTargets) {
|
|
BasicBlock *BB = JumpTarget.head();
|
|
bool IsWhitelisted = (not IsWhitelistActive or Whitelist->count(PC) != 0);
|
|
if ((CurrentCFGForm == CFGForm::SemanticPreserving
|
|
or not hasPredecessors(BB))
|
|
and IsWhitelisted) {
|
|
Targets.emplace_back(PC, BB);
|
|
}
|
|
}
|
|
|
|
constexpr auto RDHB = BlockType::RootDispatcherHelperBlock;
|
|
const auto &DispatcherInfo = PCH->buildDispatcher(Targets,
|
|
Dispatcher,
|
|
DispatcherFail,
|
|
RDHB);
|
|
DispatcherSwitch = DispatcherInfo.Switch;
|
|
|
|
// The switch is the terminator of the dispatcher basic block
|
|
setBlockType(DispatcherSwitch, BlockType::RootDispatcherBlock);
|
|
|
|
//
|
|
// Make sure every generated basic block is reachable
|
|
//
|
|
if (CurrentCFGForm != CFGForm::SemanticPreserving) {
|
|
// Compute the set of reachable jump targets
|
|
OnceQueue<BasicBlock *> WorkList;
|
|
for (BasicBlock *Successor : successors(DispatcherSwitch))
|
|
WorkList.insert(Successor);
|
|
|
|
while (not WorkList.empty()) {
|
|
BasicBlock *BB = WorkList.pop();
|
|
for (BasicBlock *Successor : make_range(succ_begin(BB), succ_end(BB)))
|
|
WorkList.insert(Successor);
|
|
}
|
|
|
|
std::set<BasicBlock *> Reachable = WorkList.visited();
|
|
|
|
// Identify all the unreachable jump targets
|
|
for (const auto &[PC, JT] : JumpTargets) {
|
|
BasicBlock *BB = JT.head();
|
|
bool IsWhitelisted = (not IsWhitelistActive or Whitelist->count(PC) != 0);
|
|
|
|
// Add to the switch all the unreachable jump targets whose reason is not
|
|
// just direct jump
|
|
if (Reachable.count(BB) == 0 and IsWhitelisted
|
|
and not JT.isOnlyReason(JTReason::DirectJump)) {
|
|
PCH->addCaseToDispatcher(DispatcherSwitch,
|
|
{ PC, BB },
|
|
BlockType::RootDispatcherHelperBlock);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool JumpTargetManager::hasPredecessors(BasicBlock *BB) const {
|
|
for (BasicBlock *Pred : predecessors(BB))
|
|
if (isTranslatedBB(Pred))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/// \brief Simple pass to drop `range` metadata, which is sometimes detrimental
|
|
class DropRangeMetadataPass : public PassInfoMixin<DropRangeMetadataPass> {
|
|
|
|
public:
|
|
PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
|
|
for (BasicBlock &BB : F)
|
|
for (Instruction &I : BB)
|
|
I.setMetadata("range", nullptr);
|
|
return PreservedAnalyses::none();
|
|
}
|
|
};
|
|
|
|
/// \brief Drop all the call to marker functions
|
|
class DropMarkerCalls : public PassInfoMixin<DropMarkerCalls> {
|
|
private:
|
|
SmallVector<StringRef, 4> ToPreserve;
|
|
|
|
public:
|
|
DropMarkerCalls(SmallVector<StringRef, 4> ToPreserve) :
|
|
ToPreserve(ToPreserve) {}
|
|
|
|
public:
|
|
PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
|
|
Module *M = F.getParent();
|
|
std::vector<CallInst *> ToErase;
|
|
|
|
for (Function &Marker : FunctionTags::Marker.functions(M)) {
|
|
//
|
|
// Check if we should preserve this marker
|
|
//
|
|
auto It = std::find(ToPreserve.begin(),
|
|
ToPreserve.end(),
|
|
Marker.getName());
|
|
auto End = std::end(ToPreserve);
|
|
if (It != End) {
|
|
Marker.setDoesNotReturn();
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Register all the calls to be erased
|
|
//
|
|
for (User *U : Marker.users())
|
|
if (auto *Call = dyn_cast<CallInst>(U))
|
|
if (Call->getParent()->getParent() == &F)
|
|
ToErase.push_back(Call);
|
|
}
|
|
|
|
//
|
|
// Actually drop the calls
|
|
//
|
|
for (CallInst *Call : ToErase)
|
|
eraseFromParent(Call);
|
|
|
|
return PreservedAnalyses::none();
|
|
}
|
|
};
|
|
|
|
namespace TrackedInstructionType {
|
|
|
|
enum Values { Invalid, WrittenInPC, StoredInMemory, StoreTarget, LoadTarget };
|
|
|
|
inline const char *getName(Values V) {
|
|
switch (V) {
|
|
case Invalid:
|
|
return "Invalid";
|
|
case WrittenInPC:
|
|
return "WrittenInPC";
|
|
case StoredInMemory:
|
|
return "StoredInMemory";
|
|
case StoreTarget:
|
|
return "StoreTarget";
|
|
case LoadTarget:
|
|
return "LoadTarget";
|
|
default:
|
|
revng_abort();
|
|
}
|
|
}
|
|
|
|
inline Values fromName(llvm::StringRef Name) {
|
|
if (Name == "Invalid")
|
|
return Invalid;
|
|
else if (Name == "WrittenInPC")
|
|
return WrittenInPC;
|
|
else if (Name == "StoredInMemory")
|
|
return StoredInMemory;
|
|
else if (Name == "StoreTarget")
|
|
return StoreTarget;
|
|
else if (Name == "LoadTarget")
|
|
return LoadTarget;
|
|
else
|
|
revng_abort();
|
|
}
|
|
|
|
} // namespace TrackedInstructionType
|
|
|
|
class AnalysisRegistry {
|
|
public:
|
|
using TrackedValueType = TrackedInstructionType::Values;
|
|
|
|
struct TrackedValue {
|
|
MetaAddress Address;
|
|
TrackedValueType Type;
|
|
Instruction *I;
|
|
};
|
|
|
|
private:
|
|
std::vector<TrackedValue> TrackedValues;
|
|
QuickMetadata QMD;
|
|
llvm::Function *AVIMarker;
|
|
IRBuilder<> Builder;
|
|
|
|
public:
|
|
AnalysisRegistry(Module *M) : QMD(getContext(M)), Builder(getContext(M)) {
|
|
AVIMarker = AdvancedValueInfoPass::createMarker(M);
|
|
}
|
|
|
|
llvm::Function *aviMarker() const { return AVIMarker; }
|
|
|
|
void registerValue(MetaAddress Address,
|
|
Value *OriginalValue,
|
|
Value *ValueToTrack,
|
|
TrackedValueType Type) {
|
|
revng_assert(Address.isValid());
|
|
|
|
Instruction *InstructionToTrack = dyn_cast<Instruction>(ValueToTrack);
|
|
if (InstructionToTrack == nullptr)
|
|
return;
|
|
|
|
revng_assert(InstructionToTrack != nullptr);
|
|
|
|
// Create the marker call and attach as second argument a unique
|
|
// identifier. This is necessary since the instruction itself could be
|
|
// deleted, duplicated and what not. Later on, we will use TrackedValues
|
|
// to now the values that have been identified to which value in the
|
|
// original function did they belong to
|
|
uint32_t AVIID = TrackedValues.size();
|
|
Builder.SetInsertPoint(InstructionToTrack->getNextNode());
|
|
Builder.CreateCall(AVIMarker,
|
|
{ InstructionToTrack, Builder.getInt32(AVIID) });
|
|
TrackedValue NewTV{ Address,
|
|
Type,
|
|
cast_or_null<Instruction>(OriginalValue) };
|
|
TrackedValues.push_back(NewTV);
|
|
}
|
|
|
|
const TrackedValue &rootInstructionById(uint32_t ID) const {
|
|
return TrackedValues.at(ID);
|
|
}
|
|
};
|
|
|
|
CallInst *JumpTargetManager::getJumpTarget(BasicBlock *Target) {
|
|
for (BasicBlock *BB : inverse_depth_first(Target)) {
|
|
if (auto *Call = dyn_cast<CallInst>(&*BB->begin())) {
|
|
auto MA = getPCFromNewPCCall(Call);
|
|
if (MA.isValid() and isJumpTarget(MA))
|
|
return Call;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
JumpTargetManager::MetaAddressSet JumpTargetManager::inflateAVIWhitelist() {
|
|
MetaAddressSet Result;
|
|
|
|
// We start from all the new basic blocks (i.e., those in
|
|
// AVIPCWhiteList) and proceed backward in the CFG in order to
|
|
// whitelist all the jump targets we meet. We stop when we meet the dispatcher
|
|
// or a function call.
|
|
|
|
// Prepare the backward visit
|
|
df_iterator_default_set<BasicBlock *> VisitSet;
|
|
|
|
// Stop at the dispatcher
|
|
VisitSet.insert(DispatcherSwitch->getParent());
|
|
|
|
// TODO: OriginalInstructionAddresses is not reliable, we should drop it
|
|
for (User *NewPCUser : TheModule.getFunction("newpc")->users()) {
|
|
auto *I = cast<Instruction>(NewPCUser);
|
|
auto WhitelistedMA = getPCFromNewPCCall(I);
|
|
if (WhitelistedMA.isValid()) {
|
|
if (AVIPCWhiteList.count(WhitelistedMA) != 0) {
|
|
BasicBlock *BB = I->getParent();
|
|
auto VisitRange = inverse_depth_first_ext(BB, VisitSet);
|
|
for (const BasicBlock *Reachable : VisitRange) {
|
|
auto MA = getPCFromNewPCCall(&*Reachable->begin());
|
|
if (MA.isValid() and isJumpTarget(MA)) {
|
|
Result.insert(MA);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
void JumpTargetManager::harvestWithAVI() {
|
|
Module *M = TheFunction->getParent();
|
|
|
|
//
|
|
// Update CPUStateAccessAnalysisPass
|
|
//
|
|
legacy::PassManager PM;
|
|
PM.add(new LoadModelWrapperPass(ModelWrapper::createConst(Model)));
|
|
PM.add(CreateCSAA());
|
|
PM.add(new FunctionCallIdentification);
|
|
PM.run(TheModule);
|
|
|
|
//
|
|
// Collect all the non-PC affecting CSVs
|
|
//
|
|
std::set<GlobalVariable *> NonPCCSVs;
|
|
QuickMetadata QMD(Context);
|
|
NamedMDNode *NamedMD = TheModule.getOrInsertNamedMetadata("revng.csv");
|
|
auto *Tuple = cast<MDTuple>(NamedMD->getOperand(0));
|
|
for (const MDOperand &Operand : Tuple->operands()) {
|
|
auto *CSV = cast<GlobalVariable>(QMD.extract<Constant *>(Operand.get()));
|
|
if (not PCH->affectsPC(CSV))
|
|
NonPCCSVs.insert(CSV);
|
|
}
|
|
|
|
//
|
|
// Clone the root function
|
|
//
|
|
Function *OptimizedFunction = nullptr;
|
|
ValueToValueMapTy OldToNew;
|
|
{
|
|
// Break all the call edges. We want to ignore those for CFG recovery
|
|
// purposes.
|
|
std::map<Use *, BasicBlock *> Undo;
|
|
auto *FunctionCall = TheModule.getFunction("function_call");
|
|
revng_assert(FunctionCall != nullptr);
|
|
for (CallBase *Call : callers(FunctionCall)) {
|
|
auto *T = Call->getParent()->getTerminator();
|
|
if (auto *Branch = dyn_cast<BranchInst>(T)) {
|
|
revng_assert(Branch->isUnconditional());
|
|
BasicBlock *Target = Branch->getSuccessor(0);
|
|
Use *U = &Branch->getOperandUse(0);
|
|
|
|
// We're after a function call: pretend we're jumping to the
|
|
// dispatcher
|
|
U->set(Dispatcher);
|
|
|
|
// Record Use for later undoing
|
|
Undo[U] = Target;
|
|
}
|
|
}
|
|
|
|
// Compute AVIJumpTargetWhitelist
|
|
auto AVIJumpTargetWhitelist = inflateAVIWhitelist();
|
|
|
|
// Prune the dispatcher
|
|
setCFGForm(CFGForm::RecoveredOnly, &AVIJumpTargetWhitelist);
|
|
|
|
// Detach all the unreachable basic blocks, so they don't get copied
|
|
std::set<BasicBlock *> UnreachableBBs = computeUnreachable();
|
|
for (BasicBlock *UnreachableBB : UnreachableBBs)
|
|
UnreachableBB->removeFromParent();
|
|
|
|
// Clone the function
|
|
OptimizedFunction = CloneFunction(TheFunction, OldToNew);
|
|
|
|
// Restore callees after function_call
|
|
for (auto [U, BB] : Undo)
|
|
U->set(BB);
|
|
|
|
// Record the size of OptimizedFunction
|
|
size_t BlocksCount = OptimizedFunction->getBasicBlockList().size();
|
|
BlocksAnalyzedByAVI.push(BlocksCount);
|
|
|
|
// Reattach the unreachable basic blocks to the original root function
|
|
for (BasicBlock *UnreachableBB : UnreachableBBs)
|
|
UnreachableBB->insertInto(TheFunction);
|
|
|
|
// Restore the dispatcher in the original function
|
|
setCFGForm(CFGForm::SemanticPreserving);
|
|
revng_assert(computeUnreachable().size() == 0);
|
|
|
|
// Clear the whitelist
|
|
AVIPCWhiteList.clear();
|
|
}
|
|
|
|
//
|
|
// Register for analysis the value written in the PC before each exit_tb call
|
|
//
|
|
AnalysisRegistry AR(M);
|
|
IRBuilder<> Builder(Context);
|
|
for (User *U : ExitTB->users()) {
|
|
if (auto *Call = dyn_cast<CallInst>(U)) {
|
|
BasicBlock *BB = Call->getParent();
|
|
if (BB->getParent() == TheFunction) {
|
|
auto It = OldToNew.find(Call);
|
|
if (It == OldToNew.end())
|
|
continue;
|
|
Builder.SetInsertPoint(cast<CallInst>(&*It->second));
|
|
Instruction *ComposedIntegerPC = PCH->composeIntegerPC(Builder);
|
|
AR.registerValue(getPC(Call).first,
|
|
Call,
|
|
ComposedIntegerPC,
|
|
TrackedInstructionType::WrittenInPC);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Register load/store addresses and PC-sized stored value
|
|
//
|
|
for (BasicBlock &BB : *OptimizedFunction) {
|
|
for (Instruction &I : BB) {
|
|
namespace TIT = TrackedInstructionType;
|
|
|
|
auto AddressType = TIT::Invalid;
|
|
Value *Pointer = nullptr;
|
|
Instruction *StoredInstruction = nullptr;
|
|
if (auto *Load = dyn_cast<LoadInst>(&I)) {
|
|
// It's a load: record the load address
|
|
Pointer = Load->getPointerOperand();
|
|
AddressType = TIT::LoadTarget;
|
|
} else if (auto *Store = dyn_cast<StoreInst>(&I)) {
|
|
// It's a store: record the store address and the stored value
|
|
Pointer = Store->getPointerOperand();
|
|
StoredInstruction = dyn_cast<Instruction>(Store->getValueOperand());
|
|
AddressType = TIT::StoreTarget;
|
|
}
|
|
|
|
// Exclude memory accesses targeting CSVs
|
|
if (Pointer != nullptr and isMemory(Pointer)) {
|
|
MetaAddress Address = getPC(&I).first;
|
|
// Register the load/store address
|
|
if (Instruction *PointerI = dyn_cast_or_null<Instruction>(Pointer))
|
|
AR.registerValue(Address, nullptr, PointerI, AddressType);
|
|
|
|
// Register the stored value, if pc-sized
|
|
Type *PointeeType = Pointer->getType()->getPointerElementType();
|
|
if (StoredInstruction != nullptr and PCH->isPCSizedType(PointeeType))
|
|
AR.registerValue(Address,
|
|
nullptr,
|
|
StoredInstruction,
|
|
TIT::StoredInMemory);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Create and initialized an alloca per CSV (except for the PC-affecting ones)
|
|
//
|
|
BasicBlock *EntryBB = &OptimizedFunction->getEntryBlock();
|
|
IRBuilder<> AllocaBuilder(&*EntryBB->begin());
|
|
IRBuilder<> InitializeBuilder(EntryBB->getTerminator());
|
|
std::map<GlobalVariable *, AllocaInst *> CSVMap;
|
|
|
|
for (GlobalVariable *CSV : NonPCCSVs) {
|
|
Type *CSVType = CSV->getType()->getPointerElementType();
|
|
auto *Alloca = AllocaBuilder.CreateAlloca(CSVType, nullptr, CSV->getName());
|
|
CSVMap[CSV] = Alloca;
|
|
|
|
// Replace all uses of the CSV within OptimizedFunction with the alloca
|
|
replaceAllUsesInFunctionWith(OptimizedFunction, CSV, Alloca);
|
|
|
|
// Initialize the alloca
|
|
InitializeBuilder.CreateStore(InitializeBuilder.CreateLoad(CSV), Alloca);
|
|
}
|
|
|
|
//
|
|
// Helper to intrinsic promotion
|
|
//
|
|
using MapperFunction = std::function<Instruction *(CallInst *)>;
|
|
std::pair<std::vector<StringRef>, MapperFunction> Mapping[] = {
|
|
{ { "helper_clz", "helper_clz32", "helper_clz64", "helper_dclz" },
|
|
[&Builder](CallInst *Call) {
|
|
return Builder.CreateBinaryIntrinsic(Intrinsic::ctlz,
|
|
Call->getArgOperand(0),
|
|
Builder.getFalse());
|
|
} }
|
|
};
|
|
|
|
for (auto &[HelperNames, Mapper] : Mapping) {
|
|
for (StringRef HelperName : HelperNames) {
|
|
if (Function *Original = M->getFunction(HelperName)) {
|
|
|
|
SmallVector<std::pair<Instruction *, Instruction *>, 16> Replacements;
|
|
for (User *U : Original->users()) {
|
|
if (auto *Call = dyn_cast<CallInst>(U)) {
|
|
if (Call->getParent()->getParent() == OptimizedFunction) {
|
|
Builder.SetInsertPoint(Call);
|
|
Instruction *NewI = Mapper(Call);
|
|
NewI->copyMetadata(*Call);
|
|
Replacements.emplace_back(Call, NewI);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply replacements
|
|
for (auto &P : Replacements) {
|
|
P.first->replaceAllUsesWith(P.second);
|
|
eraseFromParent(P.first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SmallVector<CallInst *, 16> ToErase;
|
|
for (User *U : M->getFunction("newpc")->users()) {
|
|
Instruction *I = dyn_cast<Instruction>(U);
|
|
if (I == nullptr or I->getParent()->getParent() != OptimizedFunction)
|
|
continue;
|
|
|
|
auto *Call = getCallTo(I, "newpc");
|
|
if (Call == nullptr)
|
|
continue;
|
|
|
|
PCH->expandNewPC(Call);
|
|
ToErase.push_back(Call);
|
|
}
|
|
|
|
for (CallInst *Call : ToErase)
|
|
eraseFromParent(Call);
|
|
|
|
//
|
|
// Update alias analysis
|
|
//
|
|
{
|
|
ModuleAnalysisManager MAM;
|
|
MAM.registerPass([&] {
|
|
using LMA = LoadModelAnalysis;
|
|
return LMA::fromModelWrapper(Model);
|
|
});
|
|
MAM.registerPass([&] { return GeneratedCodeBasicInfoAnalysis(); });
|
|
|
|
ModulePassManager MPM;
|
|
MPM.addPass(CSVAliasAnalysisPass());
|
|
|
|
PassBuilder PB;
|
|
PB.registerModuleAnalyses(MAM);
|
|
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
//
|
|
// Optimize the hell out of it and collect the possible values of indirect
|
|
// branches
|
|
//
|
|
|
|
using namespace model::Architecture;
|
|
using namespace model::Register;
|
|
StringRef SyscallHelperName = getSyscallHelper(Model->Architecture);
|
|
Function *SyscallHelper = M->getFunction(SyscallHelperName);
|
|
auto SyscallIDRegister = getSyscallNumberRegister(Model->Architecture);
|
|
StringRef SyscallIDCSVName = getName(SyscallIDRegister);
|
|
GlobalVariable *SyscallIDCSV = M->getGlobalVariable(SyscallIDCSVName);
|
|
|
|
SummaryCallsBuilder SCB(CSVMap);
|
|
|
|
// Remove PC initialization from entry block
|
|
{
|
|
BasicBlock &Entry = OptimizedFunction->getEntryBlock();
|
|
std::vector<Instruction *> ToDelete;
|
|
for (Instruction &I : Entry)
|
|
if (auto *Store = dyn_cast<StoreInst>(&I))
|
|
if (isa<Constant>(Store->getValueOperand()) and PCH->affectsPC(Store))
|
|
ToDelete.push_back(&I);
|
|
|
|
for (Instruction *I : ToDelete)
|
|
eraseFromParent(I);
|
|
}
|
|
|
|
{
|
|
// Note: it is important to let the pass manager go out of scope ASAP:
|
|
// LazyValueInfo registers a lot of callbacks to get notified when a
|
|
// Value is destroyed, slowing down OptimizedFunction->eraseFromParent
|
|
// enormously.
|
|
FunctionPassManager FPM;
|
|
FPM.addPass(DropMarkerCalls({ "exitTB" }));
|
|
FPM.addPass(DropHelperCallsPass(SyscallHelper, SyscallIDCSV, SCB));
|
|
FPM.addPass(ShrinkInstructionOperandsPass());
|
|
FPM.addPass(PromotePass());
|
|
FPM.addPass(InstCombinePass(true));
|
|
FPM.addPass(TypeShrinking::TypeShrinkingPass());
|
|
FPM.addPass(JumpThreadingPass());
|
|
FPM.addPass(UnreachableBlockElimPass());
|
|
FPM.addPass(InstCombinePass(true));
|
|
FPM.addPass(EarlyCSEPass(true));
|
|
FPM.addPass(DropRangeMetadataPass());
|
|
FPM.addPass(AdvancedValueInfoPass(this));
|
|
|
|
FunctionAnalysisManager FAM;
|
|
FAM.registerPass([]() { return TypeShrinking::BitLivenessPass(); });
|
|
FAM.registerPass([] {
|
|
AAManager AA;
|
|
AA.registerFunctionAnalysis<BasicAA>();
|
|
AA.registerFunctionAnalysis<ScopedNoAliasAA>();
|
|
|
|
return AA;
|
|
});
|
|
|
|
ModuleAnalysisManager MAM;
|
|
auto MAMFunactionProxyFactory = [&MAM] {
|
|
return ModuleAnalysisManagerFunctionProxy(MAM);
|
|
};
|
|
FAM.registerPass(MAMFunactionProxyFactory);
|
|
|
|
PassBuilder PB;
|
|
PB.registerFunctionAnalyses(FAM);
|
|
PB.registerModuleAnalyses(MAM);
|
|
|
|
FPM.run(*OptimizedFunction, FAM);
|
|
}
|
|
|
|
if (VerifyLog.isEnabled())
|
|
revng_check(not verifyModule(*OptimizedFunction->getParent(), &dbgs()));
|
|
|
|
//
|
|
// Collect the results
|
|
//
|
|
|
|
// Iterate over all the AVI markers
|
|
Function *AVIMarker = AR.aviMarker();
|
|
StructType *MetaAddressStruct = MetaAddress::getStruct(M);
|
|
for (User *U : AVIMarker->users()) {
|
|
auto *Call = dyn_cast<CallInst>(U);
|
|
if (Call == nullptr or skipCasts(Call->getCalledOperand()) != AVIMarker)
|
|
continue;
|
|
|
|
// Get the ID from the marker, and then the original instruction and marker
|
|
// type
|
|
uint32_t AVIID = getLimitedValue(cast<ConstantInt>(Call->getArgOperand(1)));
|
|
auto TV = AR.rootInstructionById(AVIID);
|
|
auto TIT = TV.Type;
|
|
Instruction *I = TV.I;
|
|
|
|
// Did AVI produce any info?
|
|
auto *T = dyn_cast_or_null<MDTuple>(Call->getMetadata("revng.avi"));
|
|
if (T == nullptr)
|
|
continue;
|
|
|
|
// Is this a direct write to PC?
|
|
bool IsComposedIntegerPC = (TIT == TrackedInstructionType::WrittenInPC);
|
|
|
|
// We want to register the results only if *all* of them are good
|
|
bool AllValid = true;
|
|
bool AllPCs = true;
|
|
|
|
SmallVector<MetaAddress, 16> Targets;
|
|
SmallVector<StringRef> SymbolNames;
|
|
|
|
// Iterate over all the generated values
|
|
for (const MDOperand &Operand : cast<MDTuple>(T)->operands()) {
|
|
// Extract the value
|
|
auto *Tuple = QMD.extract<MDTuple *>(Operand.get());
|
|
auto SymbolName = QMD.extract<StringRef>(Tuple->getOperand(0).get());
|
|
auto *Value = QMD.extract<ConstantInt *>(Tuple->getOperand(1).get());
|
|
|
|
bool HasDynamicSymbol = SymbolName.size() != 0;
|
|
if (HasDynamicSymbol) {
|
|
SymbolNames.push_back(SymbolName);
|
|
continue;
|
|
}
|
|
|
|
// Deserialize value into a MetaAddress, depending on the tracked
|
|
// instruction type
|
|
auto MA = (IsComposedIntegerPC ?
|
|
MetaAddress::decomposeIntegerPC(Value) :
|
|
MetaAddress::fromPC(TV.Address, getLimitedValue(Value)));
|
|
|
|
if (MA.isInvalid()) {
|
|
AllValid = false;
|
|
} else {
|
|
if (not isPC(MA))
|
|
AllPCs = false;
|
|
|
|
Targets.push_back(MA);
|
|
}
|
|
}
|
|
|
|
// We're jumping to a single symbol
|
|
bool IsPCStore = (TIT == TrackedInstructionType::WrittenInPC);
|
|
if (IsPCStore and SymbolNames.size() == 1) {
|
|
StringRef SymbolName = SymbolNames[0];
|
|
|
|
// Obtain the jump target's newpc call
|
|
CallInst *NewPCCall = getJumpTarget(I->getParent());
|
|
revng_assert(NewPCCall != nullptr);
|
|
|
|
// Set the fifth argument of newpc to the symbol
|
|
auto *IsJT = cast<ConstantInt>(NewPCCall->getArgOperand(2));
|
|
revng_assert(IsJT->getLimitedValue() == 1);
|
|
auto *SymbolVariable = buildStringPtr(M,
|
|
SymbolName,
|
|
Twine("symbol_") + SymbolName);
|
|
NewPCCall->setArgOperand(4, SymbolVariable);
|
|
}
|
|
|
|
// Proceed only if all the results are valid
|
|
if (not AllValid)
|
|
continue;
|
|
|
|
// If it's supposed to be a PC, all of them have to be a PC
|
|
bool ShouldBePC = (TIT == TrackedInstructionType::WrittenInPC
|
|
or TIT == TrackedInstructionType::StoredInMemory);
|
|
if (ShouldBePC and not AllPCs)
|
|
continue;
|
|
|
|
// Register the resulting addresses
|
|
SmallVector<Metadata *, 16> TargetsMD;
|
|
for (const MetaAddress &MA : Targets) {
|
|
switch (TIT) {
|
|
case TrackedInstructionType::WrittenInPC:
|
|
registerJT(MA, JTReason::PCStore);
|
|
break;
|
|
|
|
case TrackedInstructionType::StoredInMemory:
|
|
registerJT(MA, JTReason::MemoryStore);
|
|
break;
|
|
|
|
case TrackedInstructionType::StoreTarget:
|
|
case TrackedInstructionType::LoadTarget:
|
|
markJT(MA, JTReason::LoadAddress);
|
|
break;
|
|
|
|
case TrackedInstructionType::Invalid:
|
|
revng_abort();
|
|
}
|
|
|
|
// Turn the MetaAddress in a Constant(AsMetadata)
|
|
TargetsMD.push_back(QMD.get(MA.toConstant(MetaAddressStruct)));
|
|
}
|
|
|
|
// Save the results in root too for pinAVIResults to use
|
|
if (TIT == TrackedInstructionType::WrittenInPC) {
|
|
TV.I->setMetadata("revng.targets", QMD.tuple(TargetsMD));
|
|
}
|
|
|
|
auto OperandsCount = Targets.size();
|
|
if (OperandsCount != 0 and NewEdgesLog.isEnabled()) {
|
|
revng_log(NewEdgesLog,
|
|
OperandsCount << " targets from " << getName(Call));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Drop the optimized function
|
|
//
|
|
eraseFromParent(OptimizedFunction);
|
|
|
|
// Drop temporary functions
|
|
SCB.cleanup();
|
|
}
|
|
|
|
// Harvesting proceeds trying to avoid to run expensive analyses if not strictly
|
|
// necessary. To do this we keep in mind two aspects: do we have new basic
|
|
// blocks to visit? If so, we avoid any further anyalysis and give back control
|
|
// to the translator. If not, we proceed with other analyses until we either
|
|
// find a new basic block to translate. If we can't find a new block to
|
|
// translate we proceed as long as we are able to create new edges on the CFG
|
|
// (not considering the dispatcher).
|
|
void JumpTargetManager::harvest() {
|
|
|
|
HarvestingStats.push("harvest 0");
|
|
|
|
if (empty()) {
|
|
HarvestingStats.push("harvest 1: SimpleLiterals");
|
|
revng_log(JTCountLog, "Collecting simple literals");
|
|
for (MetaAddress PC : SimpleLiterals)
|
|
registerJT(PC, JTReason::SimpleLiteral);
|
|
SimpleLiterals.clear();
|
|
}
|
|
|
|
if (empty()) {
|
|
HarvestingStats.push("harvest 2: SROA + InstCombine + TBDP");
|
|
|
|
// Safely erase all unreachable blocks
|
|
std::set<BasicBlock *> Unreachable = computeUnreachable();
|
|
for (BasicBlock *BB : Unreachable)
|
|
BB->dropAllReferences();
|
|
for (BasicBlock *BB : Unreachable)
|
|
eraseFromParent(BB);
|
|
|
|
// TODO: move me to a commit function
|
|
|
|
// Update the third argument of newpc calls (isJT, i.e., is this instruction
|
|
// a jump target?)
|
|
IRBuilder<> Builder(Context);
|
|
Function *NewPCFunction = TheModule.getFunction("newpc");
|
|
if (NewPCFunction != nullptr) {
|
|
for (User *U : NewPCFunction->users()) {
|
|
auto *Call = cast<CallInst>(U);
|
|
if (Call->getParent() != nullptr) {
|
|
// Report the instruction on the coverage CSV
|
|
auto PC = MetaAddress::fromConstant(Call->getArgOperand(0));
|
|
|
|
bool IsJT = isJumpTarget(PC);
|
|
Call->setArgOperand(2, Builder.getInt32(static_cast<uint32_t>(IsJT)));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (VerifyLog.isEnabled())
|
|
revng_assert(not verifyModule(TheModule, &dbgs()));
|
|
|
|
revng_log(JTCountLog, "Preliminary harvesting");
|
|
|
|
HarvestingStats.push("InstCombine");
|
|
legacy::FunctionPassManager OptimizingPM(&TheModule);
|
|
OptimizingPM.add(createSROAPass());
|
|
OptimizingPM.add(createInstSimplifyLegacyPass());
|
|
OptimizingPM.doInitialization();
|
|
OptimizingPM.run(*TheFunction);
|
|
OptimizingPM.doFinalization();
|
|
|
|
legacy::PassManager PreliminaryBranchesPM;
|
|
PreliminaryBranchesPM.add(new TranslateDirectBranchesPass(this));
|
|
PreliminaryBranchesPM.run(TheModule);
|
|
|
|
if (empty()) {
|
|
HarvestingStats.push("harvest 3: harvestWithAVI");
|
|
revng_log(JTCountLog, "Harvesting with Advanced Value Info");
|
|
harvestWithAVI();
|
|
}
|
|
|
|
// TODO: eventually, `setCFGForm` should be replaced by using a CustomCFG
|
|
// To improve the quality of our analysis, keep in the CFG only the edges we
|
|
// where able to recover (e.g., no jumps to the dispatcher)
|
|
setCFGForm(CFGForm::RecoveredOnly);
|
|
|
|
NewBranches = 0;
|
|
legacy::PassManager AnalysisPM;
|
|
AnalysisPM.add(new TranslateDirectBranchesPass(this));
|
|
AnalysisPM.run(TheModule);
|
|
|
|
// Restore the CFG
|
|
setCFGForm(CFGForm::SemanticPreserving);
|
|
|
|
if (JTCountLog.isEnabled()) {
|
|
JTCountLog << std::dec << Unexplored.size() << " new jump targets and "
|
|
<< NewBranches << " new branches were found" << DoLog;
|
|
}
|
|
}
|
|
}
|
|
|
|
using BWA = JumpTargetManager::BlockWithAddress;
|
|
using JTM = JumpTargetManager;
|
|
const BWA JTM::NoMoreTargets = BWA(MetaAddress::invalid(), nullptr);
|