mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
e0c7038f46
This commit reorganizes and explains the reasoning behind the design of the RootAnalyzer optimization pipeline.
859 lines
29 KiB
C++
859 lines
29 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/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/FunctionType/Layout.h"
|
|
#include "revng/BasicAnalyses/ShrinkInstructionOperandsPass.h"
|
|
#include "revng/FunctionCallIdentification/FunctionCallIdentification.h"
|
|
#include "revng/Support/RegisterClobberer.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;
|
|
using namespace abi::FunctionType;
|
|
auto CalleeSavedRegisters = Layout::make(Prototype).CalleeSavedRegisters;
|
|
for (model::Register::Values Register : CalleeSavedRegisters)
|
|
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;
|
|
const model::TypePath &DefaultPrototype = Model->DefaultPrototype();
|
|
DefaultPreservedRegisters = getPreservedRegisters(DefaultPrototype);
|
|
|
|
RegisterClobberer Clobberer(M);
|
|
SmallVector<CallBase *, 16> FunctionCallCalls;
|
|
llvm::copy(callersIn(FunctionCall, OptimizedFunction),
|
|
std::back_inserter(FunctionCallCalls));
|
|
for (CallBase *Call : FunctionCallCalls) {
|
|
Builder.SetInsertPoint(Call);
|
|
|
|
RegisterSet *PreservedRegisters = &DefaultPreservedRegisters;
|
|
RegisterSet CalleePreservedRegisters;
|
|
|
|
// If the callee is available in the model, compute the set of preserved
|
|
// registers from its prototype, instead of using the default one
|
|
auto *Terminator = Call->getParent()->getTerminator();
|
|
if (BasicBlock *Callee = getFunctionCallCallee(Terminator)) {
|
|
MetaAddress CalleeAddress = getBasicBlockJumpTarget(Callee);
|
|
auto It = Model->Functions().find(CalleeAddress);
|
|
if (It != Model->Functions().end() and It->Prototype().isValid()) {
|
|
CalleePreservedRegisters = getPreservedRegisters(It->Prototype());
|
|
}
|
|
}
|
|
|
|
// Clobber registers that are not preserved
|
|
for (model::Register::Values Register :
|
|
model::Architecture::registers(Model->Architecture())) {
|
|
if (not PreservedRegisters->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 : 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();
|
|
}
|