mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
d25d53e711
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.
858 lines
28 KiB
C++
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();
|
|
}
|