/// \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/Definition.h" #include "revng/ABI/FunctionType/Layout.h" #include "revng/BasicAnalyses/ShrinkInstructionOperandsPass.h" #include "revng/FunctionCallIdentification/FunctionCallIdentification.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 { private: SmallVector NoReturns; public: DropMarkerCalls(SmallVector NoReturns) : NoReturns(NoReturns) {} public: PreservedAnalyses run(Function &F, FunctionAnalysisManager &) { Module *M = F.getParent(); std::vector 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 { 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 { 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(&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 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(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(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 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(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 getPreservedRegisters(const model::TypePath &Prototype) { llvm::SmallSet 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 Callees; llvm::DenseMap 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(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 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(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; 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 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 not It->Prototype().empty()) { 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; std::pair, 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, 16> Replacements; for (User *U : Original->users()) { if (auto *Call = dyn_cast(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 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 ToDelete; for (Instruction &I : Entry) if (auto *Store = dyn_cast(&I)) if (isa(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(); AA.registerFunctionAnalysis(); 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(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 Targets; QuickMetadata QMD(TheModule.getContext()); // Iterate over all the generated values for (const MDOperand &Operand : cast(T)->operands()) { // Extract the value auto *Tuple = QMD.extract(Operand.get()); auto SymbolName = QMD.extract(Tuple->getOperand(0).get()); auto *Value = QMD.extract(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(&I)) { auto *Pointer = Store->getPointerOperand(); auto *Address = dyn_cast(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(&*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 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(); }