Files
revng-revng/lib/Lift/RootAnalyzer.cpp
T
Alessandro Di Federico d25d53e711 RootAnalyzer: use generic preserved registers
Before this commit, when outlining parts of `root` for analysis
purposes, we used to replace calls with a set of instructions clobbering
the set of register *not* preserved by such call, or, in case the
prototype was not available, by the default prototype.

This commit forces the usage of the default prototype.

This trades off precision with accuracy but we get the lift pipe not to
depend on any model::Function prototype.
2024-06-20 10:24:50 +02:00

858 lines
28 KiB
C++

/// \file RootAnalyzer.cpp
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include "llvm/ADT/DepthFirstIterator.h"
#include "llvm/Analysis/BasicAliasAnalysis.h"
#include "llvm/Analysis/ScopedNoAliasAA.h"
#include "llvm/CodeGen/UnreachableBlockElim.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/IR/PatternMatch.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Transforms/InstCombine/InstCombine.h"
#include "llvm/Transforms/Scalar/EarlyCSE.h"
#include "llvm/Transforms/Scalar/JumpThreading.h"
#include "llvm/Transforms/Utils/Cloning.h"
#include "llvm/Transforms/Utils/Mem2Reg.h"
#include "revng/ABI/Definition.h"
#include "revng/ABI/FunctionType/Layout.h"
#include "revng/BasicAnalyses/ShrinkInstructionOperandsPass.h"
#include "revng/FunctionCallIdentification/FunctionCallIdentification.h"
#include "revng/Support/IRHelpers.h"
#include "revng/Support/OpaqueRegisterUser.h"
#include "revng/Support/Statistics.h"
#include "revng/TypeShrinking/BitLiveness.h"
#include "revng/TypeShrinking/TypeShrinking.h"
#include "revng/ValueMaterializer/DataFlowGraph.h"
#include "CPUStateAccessAnalysisPass.h"
#include "JumpTargetManager.h"
#include "RootAnalyzer.h"
#include "ValueMaterializerPass.h"
using namespace llvm;
RunningStatistics BlocksAnalyzedByValueMaterializer("blocks-analyzed-by-avi");
RunningStatistics WrittenInPCStatistics("written-in-pc");
RunningStatistics DetectedEdgesStatistics("detected-edges");
RunningStatistics StoredInMemoryStatistics("stored-in-memory");
RunningStatistics LoadAddressStatistics("load-address");
Logger<> NewEdgesLog("new-edges");
// NOTE: Setting this to 1 gives us performance improvement. We have tested and
// realized that there is an impact on performance if setting it to 2.
constexpr unsigned InstCombineMaxIterations = 1;
/// Drop all the call to marker functions
class DropMarkerCalls : public PassInfoMixin<DropMarkerCalls> {
private:
SmallVector<StringRef, 4> NoReturns;
public:
DropMarkerCalls(SmallVector<StringRef, 4> NoReturns) : NoReturns(NoReturns) {}
public:
PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
Module *M = F.getParent();
std::vector<CallBase *> ToErase;
for (Function &Marker : FunctionTags::Marker.functions(M)) {
StringRef MarkerName = Marker.getName();
if (llvm::count(NoReturns, MarkerName) != 0) {
// Preserve but mark as noreturn
Marker.setDoesNotReturn();
} else {
for (CallBase *Call : callersIn(&Marker, &F)) {
// Register the call to be erased
ToErase.push_back(Call);
}
}
}
//
// Actually drop the calls
//
for (CallBase *Call : ToErase)
eraseFromParent(Call);
return PreservedAnalyses::none();
}
};
/// 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::all();
}
};
/// Turn load instructions from constant addresses into constants
class ConstantLoadsFolderPass : public PassInfoMixin<ConstantLoadsFolderPass> {
private:
StaticDataMemoryOracle &MO;
public:
ConstantLoadsFolderPass(StaticDataMemoryOracle &MO) : MO(MO) {}
public:
PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
for (Instruction &I : llvm::instructions(F)) {
auto *Load = dyn_cast<LoadInst>(&I);
if (Load == nullptr)
continue;
Type *LoadType = Load->getType();
if (not LoadType->isIntegerTy())
continue;
ConstantInt *Address = nullptr;
using namespace PatternMatch;
if (not match(Load, m_Load(m_IntToPtr(m_ConstantInt(Address)))))
continue;
uint64_t LoadAddress = Address->getLimitedValue();
unsigned LoadSize = LoadType->getIntegerBitWidth() / 8;
MaterializedValue Loaded = MO.load(LoadAddress, LoadSize);
if (not Loaded.isValid() or Loaded.hasSymbol())
continue;
Load->replaceAllUsesWith(ConstantInt::get(LoadType, Loaded.value()));
}
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 *ValueMaterializerMarker;
IRBuilder<> Builder;
public:
AnalysisRegistry(Module *M) : QMD(getContext(M)), Builder(getContext(M)) {
ValueMaterializerMarker = ValueMaterializerPass::createMarker(M);
}
llvm::Function *aviMarker() const { return ValueMaterializerMarker; }
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);
unsigned MaxPhiLike = DataFlowGraph::Limits::Max;
unsigned MaxLoad = DataFlowGraph::Limits::Max;
Oracle::Values Oracle = Oracle::AdvancedValueInfo;
// Configure ValueMaterializer
switch (Type) {
case TrackedInstructionType::WrittenInPC:
// We do not think it's beneficial to traverse more than one load
MaxLoad = 1;
break;
case TrackedInstructionType::LoadTarget:
case TrackedInstructionType::StoreTarget:
// The main goal of tracking load/store addresses is to detect constant
// pools between functions. We deem it hard that the address of a constant
// pool is loaded from memory.
MaxLoad = 0;
break;
case TrackedInstructionType::StoredInMemory:
// We do not think it's beneficial to traverse more than one load
MaxLoad = 1;
// Tracking this values is a version of collecting simple literals on
// steroids. Basically we want to just track constant merged through a
// phi. No need for LazyValueInfo or AdvancedValueInfo.
Oracle = Oracle::None;
break;
default:
revng_abort();
}
// Create the marker call and attach as last 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
llvm::Module *M = ValueMaterializerMarker->getParent();
uint32_t ValueMaterializerID = TrackedValues.size();
Builder.SetInsertPoint(InstructionToTrack->getNextNode());
Builder.CreateCall(ValueMaterializerMarker,
{ InstructionToTrack,
Builder.getInt32(MaxPhiLike),
Builder.getInt32(MaxLoad),
Builder.getInt32(Oracle),
getUniqueString(M, Address.toString()),
Builder.getInt32(ValueMaterializerID) });
TrackedValue NewTV{ Address,
Type,
cast_or_null<Instruction>(OriginalValue) };
TrackedValues.push_back(NewTV);
}
const TrackedValue &rootInstructionById(uint32_t ID) const {
return TrackedValues.at(ID);
}
};
RootAnalyzer::RootAnalyzer(JumpTargetManager &JTM) :
JTM(JTM), TheModule(JTM.module()), Model(JTM.model()) {
}
RootAnalyzer::MetaAddressSet RootAnalyzer::inflateValueMaterializerWhitelist() {
MetaAddressSet Result;
// We start from all the new basic blocks (i.e., those in
// ValueMaterializerPCWhiteList) 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(JTM.dispatcher());
// TODO: OriginalInstructionAddresses is not reliable, we should drop it
for (User *NewPCUser : TheModule.getFunction("newpc")->users()) {
auto *I = cast<Instruction>(NewPCUser);
auto WhitelistedMA = addressFromNewPC(I);
if (WhitelistedMA.isValid()) {
if (JTM.isInValueMaterializerPCWhitelist(WhitelistedMA)) {
BasicBlock *BB = I->getParent();
auto VisitRange = inverse_depth_first_ext(BB, VisitSet);
for (const BasicBlock *Reachable : VisitRange) {
auto MA = getBasicBlockAddress(Reachable);
if (MA.isValid() and JTM.isJumpTarget(MA)) {
Result.insert(MA);
}
}
}
}
}
return Result;
}
// Update CPUStateAccessAnalysisPass
void RootAnalyzer::updateCSAA() {
legacy::PassManager PM;
PM.add(new LoadModelWrapperPass(ModelWrapper::createConst(Model)));
PM.add(JTM.createCSAA());
PM.add(new FunctionCallIdentification);
PM.run(TheModule);
}
static llvm::SmallSet<model::Register::Values, 16>
getPreservedRegisters(const model::TypePath &Prototype) {
llvm::SmallSet<model::Register::Values, 16> Result;
namespace FT = abi::FunctionType;
for (model::Register::Values Register : FT::calleeSavedRegisters(Prototype))
Result.insert(Register);
return Result;
}
// Clone the root function.
Function *RootAnalyzer::createTemporaryRoot(Function *TheFunction,
ValueToValueMapTy &OldToNew) {
Function *OptimizedFunction = nullptr;
Module *M = TheFunction->getParent();
// Break all the call edges. We want to ignore those for CFG recovery
// purposes.
llvm::DenseSet<BasicBlock *> Callees;
llvm::DenseMap<Use *, BasicBlock *> Undo;
auto *FunctionCall = TheModule.getFunction("function_call");
revng_assert(FunctionCall != nullptr);
for (CallBase *Call : callers(FunctionCall)) {
auto *T = Call->getParent()->getTerminator();
Callees.insert(getFunctionCallCallee(Call->getParent()));
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 anypc
U->set(JTM.anyPC());
// Record Use for later undoing
Undo[U] = Target;
}
}
// Compute ValueMaterializerJumpTargetWhitelist
auto
ValueMaterializerJumpTargetWhitelist = inflateValueMaterializerWhitelist();
// Prune the dispatcher
JTM.setCFGForm(CFGForm::NoFunctionCalls,
&ValueMaterializerJumpTargetWhitelist);
// Detach all the unreachable basic blocks, so they don't get copied
llvm::DenseSet<BasicBlock *> UnreachableBBs = JTM.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);
// Force canonical register values at the beginning of each callee
Callees.erase(nullptr);
llvm::IRBuilder<> Builder(TheModule.getContext());
for (BasicBlock *BB : Callees) {
if (OldToNew.count(BB) == 0)
continue;
BB = cast<BasicBlock>(OldToNew[BB]);
revng_assert(BB->getTerminator() != nullptr);
Builder.SetInsertPoint(BB->getFirstNonPHI());
for (const model::Segment &Segment : Model->Segments()) {
if (Segment.contains(getBasicBlockAddress(BB))) {
for (const auto &CanonicalValue : Segment.CanonicalRegisterValues()) {
auto Name = model::Register::getCSVName(CanonicalValue.Register());
if (auto *CSV = M->getGlobalVariable(Name)) {
auto *Type = getCSVType(CSV);
Builder.CreateStore(ConstantInt::get(Type, CanonicalValue.Value()),
CSV);
}
}
break;
}
}
}
//
// Turn function_call into clobbering non-callee-saved registers
//
{
// Compute preserved registers using the default prototype
using RegisterSet = llvm::SmallSet<model::Register::Values, 16>;
RegisterSet DefaultPreservedRegisters;
model::ABI::Values ABI = Model->DefaultABI();
const model::TypePath &DefaultPrototype = Model->DefaultPrototype();
if (not DefaultPrototype.empty()) {
DefaultPreservedRegisters = getPreservedRegisters(DefaultPrototype);
} else if (ABI != model::ABI::Invalid) {
for (auto &Register : abi::Definition::get(ABI).CalleeSavedRegisters())
DefaultPreservedRegisters.insert(Register);
} else {
// TODO: this must be a preliminary check
revng_abort("Either DefaultABI or DefaultPrototype needs to be "
"specified");
}
OpaqueRegisterUser Clobberer(M);
SmallVector<CallBase *, 16> FunctionCallCalls;
llvm::copy(callersIn(FunctionCall, OptimizedFunction),
std::back_inserter(FunctionCallCalls));
for (CallBase *Call : FunctionCallCalls) {
Builder.SetInsertPoint(Call);
RegisterSet CalleePreservedRegisters;
// Clobber registers that are not preserved
for (model::Register::Values Register :
model::Architecture::registers(Model->Architecture())) {
if (not DefaultPreservedRegisters.contains(Register))
Clobberer.clobber(Builder, Register);
}
}
for (CallBase *Call : FunctionCallCalls)
Call->eraseFromParent();
}
// Record the size of OptimizedFunction
size_t BlocksCount = OptimizedFunction->size();
BlocksAnalyzedByValueMaterializer.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
JTM.setCFGForm(CFGForm::SemanticPreserving);
revng_assert(JTM.computeUnreachable().size() == 0);
// Clear the whitelist
JTM.clearValueMaterializerPCWhitelist();
return OptimizedFunction;
}
// Helper to intrinsic promotion
void RootAnalyzer::promoteHelpersToIntrinsics(Function *OptimizedFunction,
IRBuilder<> &Builder) {
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 = TheModule.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);
}
}
}
}
}
RootAnalyzer::GlobalToAllocaTy
RootAnalyzer::promoteCSVsToAlloca(Function *OptimizedFunction) {
GlobalToAllocaTy CSVMap;
// Collect all the non-PC affecting CSVs
DenseSet<GlobalVariable *> NonPCCSVs;
for (GlobalVariable &CSV : FunctionTags::CSV.globals(&TheModule))
if (not JTM.programCounterHandler()->affectsPC(&CSV))
NonPCCSVs.insert(&CSV);
// Create and initialize an alloca per CSV (except for the PC-affecting ones)
BasicBlock *EntryBB = &OptimizedFunction->getEntryBlock();
IRBuilder<> AllocaBuilder(&*EntryBB->begin());
IRBuilder<> InitializeBuilder(EntryBB->getTerminator());
for (GlobalVariable *CSV : toSortedByName(NonPCCSVs)) {
Type *CSVType = CSV->getValueType();
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(createLoad(InitializeBuilder, CSV), Alloca);
}
return CSVMap;
}
SummaryCallsBuilder RootAnalyzer::optimize(llvm::Function *OptimizedFunction,
const Features &CommonFeatures) {
using namespace model::Architecture;
using namespace model::Register;
Module *M = OptimizedFunction->getParent();
auto CSVMap = promoteCSVsToAlloca(OptimizedFunction);
SummaryCallsBuilder SCB(CSVMap);
// Put together information about syscalls
StringRef SyscallHelperName = getSyscallHelper(Model->Architecture());
Function *SyscallHelper = M->getFunction(SyscallHelperName);
auto SyscallIDRegister = getSyscallNumberRegister(Model->Architecture());
StringRef SyscallIDCSVName = getName(SyscallIDRegister);
GlobalVariable *SyscallIDCSV = M->getGlobalVariable(SyscallIDCSVName);
// Remove PC initialization from entry block: this is required otherwise the
// dispatcher will be constant-propagated away
{
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 JTM.programCounterHandler()->affectsPC(Store))
ToDelete.push_back(&I);
for (Instruction *I : ToDelete)
eraseFromParent(I);
}
// The StaticDataMemoryOracle provide the contents of memory areas that are
// mapped statically (i.e., in segments). This is critical to capture, e.g.,
// virtual tables
StaticDataMemoryOracle MO(TheModule.getDataLayout(), JTM, CommonFeatures);
{
// 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.
// The order of the passes, when and how many times they are run are
// inspired by the -O2 pipeline. You can see it in action as follows:
//
// clang test.c -emit-llvm -o- -Xclang -disable-O0-optnone | \
// opt -O2 -S -debug-pass-manager
FunctionPassManager FPM;
// Drop all markers except exitTB
FPM.addPass(DropMarkerCalls({ "exitTB" }));
// Summarize calls to helpers
FPM.addPass(DropHelperCallsPass(SyscallHelper, SyscallIDCSV, SCB));
// TODO: do we still need this?
FPM.addPass(ShrinkInstructionOperandsPass());
// Canonicalization
FPM.addPass(PromotePass());
FPM.addPass(EarlyCSEPass(true));
FPM.addPass(InstCombinePass(InstCombineMaxIterations));
// This ensures we have in the IR values from constant pools, which will
// then get collected by collectValuesStoredIntoMemory
FPM.addPass(ConstantLoadsFolderPass(MO));
// Running JumpThreading is important to merge multiple instructions with
// the same predicate in a single "if" (in particular in ARM) and obtain
// more accurate constraints.
FPM.addPass(JumpThreadingPass());
// Shrink instructions
// InstCombine should not run after TypeShrinking since it undoes its work.
// Specifically it turns icmp that have been shrank to 32-bit by
// TypeShrinking back to 64-bits.
FPM.addPass(TypeShrinking::TypeShrinkingPass());
// It is important to run EarlyCSE *after* JumpThreading. This has the
// side effect of invalidating LazyValueInfo (which would otherwise be
// shared between JumpThreadingPass and ValueMaterializerPass).
// If we don't run it we get failures on ARM.
// It is also important to run EarlyCSE after TypeShrinking to factor trunc
// instructions and have more accurate constraints.
FPM.addPass(EarlyCSEPass(true));
// Drop range metadata
FPM.addPass(DropRangeMetadataPass());
// Run ValueMaterializer!
FPM.addPass(ValueMaterializerPass(MO));
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);
}
return SCB;
}
void RootAnalyzer::collectMaterializedValues(AnalysisRegistry &AR) {
// Iterate over all the ValueMaterializer markers
Function *ValueMaterializerMarker = AR.aviMarker();
for (CallBase *Call : callers(ValueMaterializerMarker)) {
// Get the ID from the marker, and then the original instruction and marker
// type
Value *LastArgument = Call->getArgOperand(Call->arg_size() - 1);
uint32_t ValueMaterializerID = getLimitedValue(LastArgument);
auto TV = AR.rootInstructionById(ValueMaterializerID);
auto TIT = TV.Type;
Instruction *I = TV.I;
// Did ValueMaterializer 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;
QuickMetadata QMD(TheModule.getContext());
// 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 (not HasDynamicSymbol) {
// 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 JTM.isPC(MA))
AllPCs = false;
Targets.push_back(MA);
}
}
}
// 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
unsigned RegisteredAddresses = 0;
auto RegisterJT = [this, &RegisteredAddresses](MetaAddress Address,
JTReason::Values Reason) {
bool IsNew = not JTM.hasJT(Address);
if (JTM.registerJT(Address, Reason) != nullptr and IsNew) {
++RegisteredAddresses;
}
};
switch (TIT) {
case TrackedInstructionType::WrittenInPC:
for (const MetaAddress &MA : Targets)
RegisterJT(MA, JTReason::PCStore);
WrittenInPCStatistics.push(RegisteredAddresses);
break;
case TrackedInstructionType::StoredInMemory:
for (const MetaAddress &MA : Targets)
RegisterJT(MA, JTReason::MemoryStore);
StoredInMemoryStatistics.push(RegisteredAddresses);
break;
case TrackedInstructionType::StoreTarget:
case TrackedInstructionType::LoadTarget:
for (const MetaAddress &MA : Targets)
if (JTM.markJT(MA, JTReason::LoadAddress))
++RegisteredAddresses;
LoadAddressStatistics.push(RegisteredAddresses);
break;
case TrackedInstructionType::Invalid:
revng_abort();
}
if (TIT == TrackedInstructionType::WrittenInPC) {
// This is a call to `exit_tb`, transfer the revng.avi metadata on the
// call as revng.targets for later processing
revng_assert(TV.I != nullptr);
TV.I->setMetadata("revng.targets", T);
DetectedEdgesStatistics.push(Targets.size());
revng_log(NewEdgesLog,
Targets.size() << " targets from " << getName(Call));
}
}
}
using JTM2 = RootAnalyzer;
void JTM2::collectValuesStoredIntoMemory(Function *F,
const Features &CommonFeatures) {
for (Instruction &I : llvm::instructions(F)) {
if (auto *Store = dyn_cast<StoreInst>(&I)) {
auto *Pointer = Store->getPointerOperand();
auto *Address = dyn_cast<ConstantInt>(Store->getValueOperand());
if (isMemory(Pointer) and Address != nullptr
and JTM.programCounterHandler()->isPCSizedType(Address->getType())) {
auto MA = MetaAddress::fromPC(Address->getLimitedValue(),
CommonFeatures);
if (MA.isValid()) {
JTM.registerJT(MA, JTReason::MemoryStore);
}
}
}
}
}
static MetaAddress::Features findCommonFeatures(Function *F) {
bool First = true;
MetaAddress::Features Result;
for (CallBase *NewPCCall :
callersIn(F->getParent()->getFunction("newpc"), F)) {
MetaAddress Address = addressFromNewPC(NewPCCall);
if (First) {
Result = Address.features();
} else {
// TODO: once we switch to multi-binary run ValueMaterializer once per
// each feature set
revng_assert(Result == Address.features());
}
}
return Result;
}
void RootAnalyzer::cloneOptimizeAndHarvest(Function *TheFunction) {
updateCSAA();
ValueToValueMapTy OldToNew;
Function *OptimizedFunction = createTemporaryRoot(TheFunction, OldToNew);
MetaAddress::Features CommonFeatures = findCommonFeatures(OptimizedFunction);
AnalysisRegistry AR(&TheModule);
// Register for analysis the value written in the PC before each exit_tb call
IRBuilder<> Builder(TheModule.getContext());
for (CallBase *Call : callersIn(JTM.exitTB(), TheFunction)) {
BasicBlock *BB = Call->getParent();
auto It = OldToNew.find(Call);
if (It == OldToNew.end())
continue;
Builder.SetInsertPoint(cast<CallInst>(&*It->second));
ProgramCounterHandler *PCH = JTM.programCounterHandler();
Instruction *ComposedIntegerPC = PCH->composeIntegerPC(Builder);
AR.registerValue(getPC(Call).first,
Call,
ComposedIntegerPC,
TrackedInstructionType::WrittenInPC);
}
promoteHelpersToIntrinsics(OptimizedFunction, Builder);
// Replace calls to newpc with stores to the PC
SmallVector<CallBase *, 16> ToErase;
for (CallBase *Call :
callersIn(TheModule.getFunction("newpc"), OptimizedFunction)) {
JTM.programCounterHandler()->expandNewPC(Call);
ToErase.push_back(Call);
}
for (CallBase *Call : ToErase)
eraseFromParent(Call);
// Optimize the hell out of it and collect the possible values of indirect
// branches.
auto SCB = optimize(OptimizedFunction, CommonFeatures);
revng::verify(OptimizedFunction);
// Collect the results
collectMaterializedValues(AR);
// Collect pointer-sized values being stored in memory
collectValuesStoredIntoMemory(OptimizedFunction, CommonFeatures);
// Drop the optimized function
eraseFromParent(OptimizedFunction);
// Drop temporary functions
SCB.cleanup();
}