// // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include #include #include #include #include #include #include #include #include #include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SetVector.h" #include "llvm/ADT/iterator_range.h" #include "llvm/Analysis/BasicAliasAnalysis.h" #include "llvm/Analysis/GlobalsModRef.h" #include "llvm/Analysis/ScopedNoAliasAA.h" #include "llvm/Analysis/TypeBasedAliasAnalysis.h" #include "llvm/CodeGen/CodeGenPassBuilder.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/CFG.h" #include "llvm/IR/DataLayout.h" #include "llvm/IR/DebugLoc.h" #include "llvm/IR/Function.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/ModuleSlotTracker.h" #include "llvm/IR/PassManager.h" #include "llvm/IR/PatternMatch.h" #include "llvm/IR/Verifier.h" #include "llvm/Pass.h" #include "llvm/PassInfo.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Support/Debug.h" #include "llvm/Support/ModRef.h" #include "llvm/Support/TypeSize.h" #include "revng/ABI/FunctionType/Layout.h" #include "revng/ABI/ModelHelpers.h" #include "revng/ADT/GenericGraph.h" #include "revng/ADT/SmallMap.h" #include "revng/Canonicalize/SwitchToStatements.h" #include "revng/InitModelTypes/InitModelTypes.h" #include "revng/LocalVariables/LocalVariableBuilder.h" #include "revng/MFP/MFP.h" #include "revng/MFP/SetLattices.h" #include "revng/Model/Binary.h" #include "revng/Model/FunctionTags.h" #include "revng/Model/IRHelpers.h" #include "revng/Model/LoadModelPass.h" #include "revng/Support/BlockType.h" #include "revng/Support/Debug.h" #include "revng/Support/DecompilationHelpers.h" #include "revng/Support/IRBuilder.h" #include "revng/Support/IRHelpers.h" static Logger Log{ "switch-to-statements" }; using namespace llvm; // // Templated using for types of local variables, copy and assign instructions. // These are only necessary because we still have the legacy version. // We can drop the template when we drop legacy mode, and just use StoreInst for // AssignType, LoadInst for CopyType, and AllocaInst for LocalVarType. // template using AssignType = std::conditional_t; template using CopyType = std::conditional_t; template using LocalVarType = std::conditional_t; // // Templated helpers for getting pointer and value operands for copy and assign // instructions. // These are only necessary because we still have the legacy version where a // copy is not just a LoadInst but a call to an opaque function, and an // assignment is not just a StoreInst but it's also a call to another opaque // function. // We can drop the template when we drop legacy mode, and just remove these // helpers or at lease greatly simplify them. // template static Use *getStorePointerOperandUse(Instruction *I) { if (not I) return nullptr; if constexpr (IsLegacy) { if (CallInst *Assign = getCallToTagged(I, FunctionTags::Assign)) return &Assign->getArgOperandUse(1); } else { if (auto *Assign = dyn_cast(I)) return &Assign->getOperandUse(1); } return nullptr; } template static const Use *getStorePointerOperandUse(const Instruction *I) { if (not I) return nullptr; if constexpr (IsLegacy) { if (const CallInst *Assign = getCallToTagged(I, FunctionTags::Assign)) return &Assign->getArgOperandUse(1); } else { if (const auto *Assign = dyn_cast(I)) return &Assign->getOperandUse(1); } return nullptr; } template static Value *getStorePointerOperand(Instruction *I) { Use *U = getStorePointerOperandUse(I); return U ? U->get() : nullptr; } template static const Value *getStorePointerOperand(const Instruction *I) { const Use *U = getStorePointerOperandUse(I); return U ? U->get() : nullptr; } template static bool isStorePointerOperand(const Use &U, const Instruction *I) { return getStorePointerOperandUse(I) == &U; } template static Use *getStoreValueOperandUse(Instruction *I) { if (not I) return nullptr; if constexpr (IsLegacy) { if (CallInst *Assign = getCallToTagged(I, FunctionTags::Assign)) return &Assign->getArgOperandUse(0); } else { if (auto *Assign = dyn_cast(I)) return &Assign->getOperandUse(0); } return nullptr; } template static const Use *getStoreValueOperandUse(const Instruction *I) { if (not I) return nullptr; if constexpr (IsLegacy) { if (const CallInst *Assign = getCallToTagged(I, FunctionTags::Assign)) return &Assign->getArgOperandUse(0); } else { if (const auto *Assign = dyn_cast(I)) return &Assign->getOperandUse(0); } return nullptr; } template static Value *getStoreValueOperand(Instruction *I) { Use *U = getStoreValueOperandUse(I); return U ? U->get() : nullptr; } template static const Value *getStoreValueOperand(const Instruction *I) { const Use *U = getStoreValueOperandUse(I); return U ? U->get() : nullptr; } template static bool isStoreValueOperand(const Use &U, const Instruction *I) { return getStoreValueOperandUse(I) == &U; } template static Use *getLoadPointerOperandUse(Instruction *I) { if (not I) return nullptr; if constexpr (IsLegacy) { if (CallInst *Assign = getCallToTagged(I, FunctionTags::Copy)) return &Assign->getArgOperandUse(0); } else { if (auto *Assign = dyn_cast(I)) return &Assign->getOperandUse(0); } return nullptr; } template static const Use *getLoadPointerOperandUse(const Instruction *I) { if (not I) return nullptr; if constexpr (IsLegacy) { if (const CallInst *Assign = getCallToTagged(I, FunctionTags::Copy)) return &Assign->getArgOperandUse(0); } else { if (const auto *Assign = dyn_cast(I)) return &Assign->getOperandUse(0); } return nullptr; } template static Value *getLoadPointerOperand(Instruction *I) { Use *U = getLoadPointerOperandUse(I); return U ? U->get() : nullptr; } template static const Value *getLoadPointerOperand(const Instruction *I) { const Use *U = getLoadPointerOperandUse(I); return U ? U->get() : nullptr; } template static Value *getPointerOperand(Instruction *I) { if (auto *V = getLoadPointerOperand(I)) return V; if (auto *V = getStorePointerOperand(I)) return V; return nullptr; } template static const Value *getPointerOperand(const Instruction *I) { if (const auto *V = getLoadPointerOperand(I)) return V; if (const auto *V = getStorePointerOperand(I)) return V; return nullptr; } // // Helpers for statements and side effects. // // TODO: drop this when we drop legacy mode, since in non-legacy mode this is a // separate pass already. static bool causesExponentialDataflowPaths(const Instruction *I) { // This forces all various kinds of instructions to get their value stored // into a local variable. // The reason for doing this is that these instructions often contribute to // the formation of pathological dataflows, causing exponential path explosion // when expanded into C expressions during decompilation. // Serializing their value into a dedicated local variable breaks such an // exponential path explosion. // // This is a temporary workaround, that we've already put in place for // SelectInst too, and that will be replaced in the future by a more // principled approach for splitting pathological dataflows that lead to // exponential path esplosion. if (isa(I)) return true; Value *Op0 = nullptr; Value *Op1 = nullptr; Value *Op2 = nullptr; using namespace llvm::PatternMatch; if (match(I, m_FShl(m_Value(Op0), m_Value(Op1), m_Value(Op2)))) return true; if (match(I, m_FShr(m_Value(Op0), m_Value(Op1), m_Value(Op2)))) return true; return false; } static bool doesNotAccessMemory(const Instruction *I) { // We have to hardcode revng_call_stack_arguments and revng_stack_frame // because SegregateStackAccesses has to mark them as functions that read // inaccessible memory, in order to prevent some LLVM optimizations. // Same for OpaqueExtractValue. if (auto *Call = dyn_cast(I)) { if (Function *Callee = getCalledFunction(Call)) { StringRef Name = Callee->getName(); if (Name.startswith("revng_call_stack_arguments") or Name.startswith("revng_stack_frame")) { return true; } } if (getCallToTagged(I, FunctionTags::OpaqueExtractValue)) return true; if (getCallToTagged(I, FunctionTags::StructInitializer)) return true; } return false; } template static bool mayHaveSideEffects(const Instruction *I) { if (IsLegacy and causesExponentialDataflowPaths(I)) return true; if (doesNotAccessMemory(I)) return false; return I->mayHaveSideEffects(); } template static bool mayWriteToMemory(const Instruction *I) { if (IsLegacy and causesExponentialDataflowPaths(I)) return true; if (doesNotAccessMemory(I)) return false; return I->mayWriteToMemory(); } static bool mayReadMemory(const Instruction *I) { if (doesNotAccessMemory(I)) return false; return I->mayReadFromMemory(); } // // Legacy mode helpers for traversing ModelGEPs and discovering the local // variable accessed by an Instruction. // They can be dropped when legacy mode goes away. // static RecursiveCoroutine> getAccessedLocalVariableFromModelGEP(const CallInst *ModelGEPRefCall) { revng_assert(isCallToTagged(ModelGEPRefCall, FunctionTags::ModelGEPRef)); revng_assert(ModelGEPRefCall->arg_size() >= 2); // If the ModelGEPRefCall has more than 2 arguments, and some of them are not // constants, we cannot figure out all the list of potentially accessed local // variables, so we just return nullptr. for (const Use &GEPArg : llvm::drop_begin(ModelGEPRefCall->args(), 2)) { if (not isa(GEPArg.get())) rc_return nullptr; } // If the Base argument of the ModelGEPRefCall isn't a LocalVariable, nor an // Argument, nor another ModelGEPRef, we just return nullopt, meaning that // this thing doesn't really access any local variable. auto *GEPBase = ModelGEPRefCall->getArgOperand(1); // If the GEPBase is directly an argument, we're done if (isa(GEPBase)) rc_return GEPBase; // If the GEPBase is directly a LocalVariable, we're done if (isCallToTagged(GEPBase, FunctionTags::AllocatesLocalVariable)) rc_return GEPBase; // If the GEPBase is another ModelGEPRef we recur. // Notice that we don't recur on ModelGEP, only on ModelGEPRef, because simple // ModelGEP can have arbitrary base pointers, but they never access // LocalVariables. if (auto *NestedModelGEPRef = getCallToTagged(GEPBase, FunctionTags::ModelGEPRef)) rc_return rc_recur getAccessedLocalVariableFromModelGEP(NestedModelGEPRef); // Everything else cannot access local variables, so we return nullopt. rc_return std::nullopt; } // Get the local variable accessed by I. // If the returned optional is nullopt, it means that I doesn't accessy memory. // If the returned optional is engaged: // - if it holds a null pointer, it means that I accesses memory but we weren't // able to figure out where // - if it holds a valid pointer, it must be either an Argument or the accessed // local variable static std::optional getAccessedLegacyLocal(const Instruction *I) { const CopyType *Copy = getCallToTagged(I, FunctionTags::Copy); const AssignType *Assign = getCallToTagged(I, FunctionTags::Assign); // If it's not a Copy not an Assign then it's not an access to a local // variable. // TODO: this doesn't take into consideration stuff like memcpy. if (not Copy and not Assign) return std::nullopt; const CallInst *AccessCall = Copy ? Copy : Assign; unsigned AccessArgumentNumber = Assign ? 1 : 0; const auto *Accessed = AccessCall->getArgOperand(AccessArgumentNumber); // If the accessed thing is directly an Argument or a LocalVariable we're // done. if (isa(Accessed) or isCallToTagged(Accessed, FunctionTags::AllocatesLocalVariable)) { return Accessed; } // If the accessed thing is not a ModelGEPRef, then it's not an access to a // local variable. auto *ModelGEPRef = getCallToTagged(Accessed, FunctionTags::ModelGEPRef); if (not ModelGEPRef) return std::nullopt; return getAccessedLocalVariableFromModelGEP(ModelGEPRef); } /// A class that represents an available expression, along with the assignment /// that writes its value somewhere, making it available. template struct AvailableExpression { using AssignType = AssignType; // The expression that is available Instruction *Expression = nullptr; // The Assign/Store that has assigned the Expression to some location. // It can be used to retrieve the address of the location itself. // nullptr means that we don't have a specific address but the Expression // itself can be computed at the given program point without breaking // semantics. AssignType *Assignment = nullptr; bool operator==(const AvailableExpression &) const = default; std::strong_ordering operator<=>(const AvailableExpression &) const = default; }; template using AvailableSet = std::set>; template static auto findAvailableRange(const AvailableSet &Availables, Instruction *I) { using AvailableExpression = AvailableExpression; auto Begin = Availables.lower_bound(AvailableExpression{ .Expression = I, .Assignment = nullptr }); auto End = Availables.upper_bound(AvailableExpression{ .Expression = std::next(I), .Assignment = nullptr }); return llvm::make_range(Begin, End); } constexpr size_t SmallSize = 8; using InstructionVector = SmallVector; using InstructionSetVector = SmallSetVector; struct ProgramPointData { Instruction *TheInstruction = nullptr; ProgramPointData(Instruction *I) : TheInstruction(I){}; }; using ProgramPointNode = BidirectionalNode; using ProgramPointsCFG = GenericGraph; template struct AvailableExpressionsMonotoneFramework; template struct AvailableExpressionsMonotoneFramework { public: using GraphType = ProgramPointsCFG *; using LatticeElement = AvailableSet; using Label = ProgramPointNode *; private: AliasAnalysis *AA; ModuleSlotTracker &MST; public: AvailableExpressionsMonotoneFramework(AliasAnalysis *A, ModuleSlotTracker &TheMST) : AA(A), MST(TheMST) {} public: LatticeElement combineValues(const LatticeElement &LHS, const LatticeElement &RHS) const { return SetIntersectionLattice::combineValues(LHS, RHS); } bool isLessOrEqual(const LatticeElement &LHS, const LatticeElement &RHS) const { return SetIntersectionLattice::isLessOrEqual(LHS, RHS); } LatticeElement applyTransferFunction(ProgramPointNode *L, const LatticeElement &E, mfp::NoExtraState &ExtraState) const; private: void applyTransferFunctionImpl(Instruction *I, LatticeElement &E) const; bool mayClobber(Instruction *Access, Instruction *Affected) const; }; template using AEMFP = AvailableExpressionsMonotoneFramework; template using LatticeElement = AEMFP::LatticeElement; template using AvailableExpressionsMap = mfp::MFIResultMap>; static bool legacyLocalVariablesNoAlias(const Instruction *I, const Instruction *J) { // Copies from local variables never alias anyone else, except other // instructions that copy or assign the same local variable std::optional MayBeAccessedByI = getAccessedLegacyLocal(I); std::optional MayBeAccessedByJ = getAccessedLegacyLocal(J); // If either doesn't access a local variable, they are noAlias. if (not MayBeAccessedByI.has_value() or not MayBeAccessedByJ.has_value()) return true; const Value *AccessedByI = *MayBeAccessedByI; const Value *AccessedByJ = *MayBeAccessedByJ; // If either is nullptr, there is at least one among I and J that access many // variables, and we just can't say with certainty that they are noAlias if (nullptr == AccessedByI or nullptr == AccessedByJ) return false; // If both are arguments, they may point overlapping memory, and we have no // way of knowing. So we return false, because we're not sure they don't // alias. if (isa(AccessedByI) and isa(AccessedByJ)) return false; // For all the other cases they are noAlias only if the accessed // local variable is different. return AccessedByI != AccessedByJ; } template bool AEMFP::mayClobber(Instruction *Access, Instruction *Affected) const { revng_log(Log, "mayClobber"); LoggerIndent Indent{ Log }; revng_log(Log, "Access: " << dumpToString(Access, MST)); revng_log(Log, "Affected: " << dumpToString(Affected, MST)); LoggerIndent MoreIndent{ Log }; bool Result = true; if constexpr (IsLegacy) { // If either instruction doesn't access memory, they are noAlias for sure. if (not Access->mayReadOrWriteMemory()) { revng_log(Log, "Access->mayReadOrWriteMemory() == false"); Result = false; } else if (not Affected->mayReadOrWriteMemory()) { revng_log(Log, "Affected->mayReadOrWriteMemory() == false"); return false; } else { // Here both instructions access memory. // Just handle LocalVariables specifically. // TODO: this is a poor's man alias analysis, which only explicitly // handles stuff that is frequent and that we care about. In the future we // have plans to replace it with a full fledged AliasAnalysis from LLVM Result = not legacyLocalVariablesNoAlias(Access, Affected); } } else { Result = isModSet(AA->getModRefInfo(Access, Affected)); } revng_log(Log, "Access may " << (Result ? std::string() : std::string("not ")) << "clobber Affected"); return Result; } template void AEMFP::applyTransferFunctionImpl(Instruction *I, LatticeElement &E) const { using AvailableExpression = AvailableExpression; using AssignType = AssignType; revng_log(Log, "applyTransferFunction on Instruction I: " << dumpToString(I, MST)); LoggerIndent Indent{ Log }; if constexpr (IsLegacy) { revng_assert(not isa(I) and not isa(I)); } else { revng_assert(not isCallToTagged(I, FunctionTags::Copy) and not isCallToTagged(I, FunctionTags::Assign)); } if (mayHaveSideEffects(I)) { revng_log(Log, "mayHaveSideEffects"); LoggerIndent XX{ Log }; for (const AvailableExpression &A : llvm::make_early_inc_range(E)) { const auto &[Available, Assign] = A; revng_log(Log, "Available: " << dumpToString(Available, MST)); revng_log(Log, "Assign: " << dumpToString(Assign, MST)); LoggerIndent XXX{ Log }; if (mayClobber(I, Available)) { revng_log(Log, "I may clobber Available: " << dumpToString(Available, MST)); revng_log(Log, "erase Available"); E.erase(A); } else if (Assign and mayClobber(I, Assign)) { revng_log(Log, "I may clobber Assign: " << dumpToString(Assign, MST)); revng_log(Log, "erase Available"); E.erase(A); } } } auto *StoredOperand = getStoreValueOperand(I); auto *AssignedInstruction = dyn_cast_or_null(StoredOperand); if (AssignedInstruction) { revng_log(Log, "I is Assign"); revng_log(Log, "insert Available: " << dumpToString(AssignedInstruction, MST)); revng_log(Log, " Assign: " << dumpToString(I, MST)); E.insert(AvailableExpression{ .Expression = AssignedInstruction, .Assignment = cast(I), }); } if (mayReadMemory(I)) { revng_log(Log, "mayReadMemory -> insert Available: I"); E.insert(AvailableExpression{ .Expression = I, .Assignment = nullptr, }); } } template AEMFP::LatticeElement AEMFP::applyTransferFunction(ProgramPointNode *ProgramPoint, const AEMFP::LatticeElement &E, mfp::NoExtraState &ExtraState) const { Instruction *I = ProgramPoint->TheInstruction; revng_log(Log, "applyTransferFunction on ProgramPoint: " << dumpToString(I, MST)); LoggerIndent Indent{ Log }; LatticeElement Result = E; revng_log(Log, "initial set"); if (Log.isEnabled()) { LoggerIndent ModeIndent{ Log }; for (const auto &[Available, Assign] : Result) { revng_log(Log, "Available: " << dumpToString(Available, MST)); revng_log(Log, "Assign: " << dumpToString(Assign, MST)); } } applyTransferFunctionImpl(I, Result); revng_log(Log, "final set"); if (Log.isEnabled()) { LoggerIndent ModeIndent{ Log }; for (const auto &[Available, Assign] : Result) { revng_log(Log, "Available: " << dumpToString(Available, MST)); revng_log(Log, "Assign: " << dumpToString(Assign, MST)); } } return Result; } // // Helpers for identifying program points that are relevant for available // expressions. // template static bool isProgramPoint(const Instruction *I) { const Instruction *UnexpectedInstruction = nullptr; if constexpr (IsLegacy) { // Legacy mode just assumes that we don't have Load/Store/Alloca at all. if (isa(I) or isa(I) or isa(I) or isa(I)) UnexpectedInstruction = I; } else { // Non-legacy mode assumes that most custom opcode don't exist. Some of them // have been replaced by Load/Store/Alloca, and others have been dropped // because in the clift-based pipeline they will be only materialized in // Clift as regular operators, so we don't need them in LLVM anymore and we // want to make sure they disappear over time until we can actually drop // them. if (isCallToTagged(I, FunctionTags::AllocatesLocalVariable) or isCallToTagged(I, FunctionTags::LocalVariable) or isCallToTagged(I, FunctionTags::Copy) or isCallToTagged(I, FunctionTags::Assign) or isCallToTagged(I, FunctionTags::AddressOf) or isCallToTagged(I, FunctionTags::Marker) or isCallToTagged(I, FunctionTags::IsRef) or isCallToTagged(I, FunctionTags::StringLiteral) or isCallToTagged(I, FunctionTags::ModelCast) or isCallToTagged(I, FunctionTags::ModelGEP) or isCallToTagged(I, FunctionTags::ModelGEPRef) or isCallToTagged(I, FunctionTags::Parentheses) or isCallToTagged(I, FunctionTags::LiteralPrintDecorator) or isCallToTagged(I, FunctionTags::HexInteger) or isCallToTagged(I, FunctionTags::CharInteger) or isCallToTagged(I, FunctionTags::BoolInteger) or isCallToTagged(I, FunctionTags::NullPtr) or isCallToTagged(I, FunctionTags::SegmentRef) or isCallToTagged(I, FunctionTags::UnaryMinus) or isCallToTagged(I, FunctionTags::BinaryNot) or isCallToTagged(I, FunctionTags::BooleanNot)) { UnexpectedInstruction = I; } } if (nullptr != UnexpectedInstruction) { I->dump(); revng_abort("Unexpected Instruction"); } return I == &I->getParent()->front() or mayHaveSideEffects(I) or mayReadMemory(I); } template static InstructionSetVector getProgramPoints(BasicBlock &B) { InstructionSetVector Results; for (Instruction &I : B) if (isProgramPoint(&I)) Results.insert(&I); return Results; } using InstructionProgramPoint = std::unordered_map; // An extended version of ProgramPointsCFG, that holds a graph of statements // points, along with a map from each Instruction to its previous statement. template class AvailableExpressionsResult { public: using AvailableExpression = AvailableExpression; using AvailableSet = AvailableSet; using AvailableExpressionsMap = AvailableExpressionsMap; public: ProgramPointsCFG ProgramPointsGraph; AvailableExpressionsMap AvailableExpressions; private: // Map an Instruction to its associated program point in ProgramPointsGraph InstructionProgramPoint ProgramPoint; // Map an Instruction to its associated previous program point in // ProgramPointsGraph. InstructionProgramPoint PreviousProgramPointInBlock; // Map an Instruction to its associated next program point in // ProgramPointsGraph. InstructionProgramPoint NextProgramPointInBlock; ModuleSlotTracker &MST; public: AvailableExpressionsResult(ModuleSlotTracker &TheMST) : MST(TheMST) {} // Factory from llvm::Function static AvailableExpressionsResult makeFromFunction(Function &F, ModuleSlotTracker &MST) { SmallMap, 8> BlockToBeginEndNode; AvailableExpressionsResult Result(MST); ProgramPointsCFG &TheCFG = Result.ProgramPointsGraph; InstructionProgramPoint &ProgramPoint = Result.ProgramPoint; InstructionProgramPoint &PreviousProgramPointInBlock = Result.PreviousProgramPointInBlock; InstructionProgramPoint &NextProgramPointInBlock = Result.NextProgramPointInBlock; const auto MakeCFGNode = [&TheCFG, &ProgramPoint](Instruction *I) { ProgramPointNode *NewNode = TheCFG.addNode(I); ProgramPoint[I] = NewNode; return NewNode; }; for (BasicBlock &BB : F) { InstructionSetVector ProgramPoints = getProgramPoints(BB); // Reserve space for the new ProgramPoints. This is for performance but // also for stability of pointers while adding new nodes, which allows to // also save pointers to begin and end nodes of each block in a map, to // handle addition of inter-block edges. If we don't reserve the pointers // returned by addNode aren't stable and the trick for adding inter-block // edges doesn't work. TheCFG.reserve(TheCFG.size() + ProgramPoints.size()); ProgramPointNode *FirstNode = MakeCFGNode(ProgramPoints.front()); ProgramPointNode *LastNode = FirstNode; for (Instruction &I : llvm::make_range(BB.begin(), ProgramPoints.front()->getIterator())) NextProgramPointInBlock[&I] = LastNode; auto ProgramPointPairs = llvm::zip_equal(llvm::drop_end(ProgramPoints), llvm::drop_begin(ProgramPoints)); for (const auto &[PreviousProgramPoint, NextProgramPoint] : ProgramPointPairs) { // Create a new node. ProgramPointNode *NewNode = MakeCFGNode(NextProgramPoint); // We can already add intra-block edges. LastNode->addSuccessor(NewNode); // Now we have to initialize PreviousProgramPointInBlock for all the // instructions that are not program points and that are among the // previous program point and the current new one. for (Instruction &I : llvm::make_range(std::next(PreviousProgramPoint->getIterator()), NextProgramPoint->getIterator())) PreviousProgramPointInBlock[&I] = LastNode; // Finally we can update the LastNode. LastNode = NewNode; } for (Instruction &I : llvm::make_range(std::next(ProgramPoints.back()->getIterator()), BB.end())) PreviousProgramPointInBlock[&I] = LastNode; BlockToBeginEndNode[&BB] = { FirstNode, LastNode }; } // Now we add the inter-block edges. for (BasicBlock &BB : F) for (BasicBlock *Successor : llvm::successors(&BB)) BlockToBeginEndNode.at(&BB) .second->addSuccessor(BlockToBeginEndNode.at(Successor).first); // And set the entry node, which makes the MFP later more efficient, because // it allows the algorithm to take the structure of the graph into account, // instead of iterating in sparse order. TheCFG.setEntryNode(BlockToBeginEndNode.at(&F.getEntryBlock()).first); return Result; } public: auto getAvailableAt(Instruction *I, const Instruction *Where) const { revng_log(Log, "IsAvailableAt"); revng_log(Log, "Available?: " << dumpToString(I, MST)); revng_log(Log, "Where: " << dumpToString(Where, MST)); auto ProgramPointIt = ProgramPoint.find(Where); if (ProgramPointIt != ProgramPoint.end()) { revng_log(Log, "is ProgramPoint"); ProgramPointNode *UserProgramPoint = ProgramPointIt->second; const AvailableSet &Available = AvailableExpressions.at(UserProgramPoint) .InValue; return findAvailableRange(Available, I); } auto PreviousPointIt = PreviousProgramPointInBlock.find(Where); if (PreviousPointIt != PreviousProgramPointInBlock.end()) { revng_log(Log, "is NOT ProgramPoint"); ProgramPointNode *UserProgramPoint = PreviousPointIt->second; revng_log(Log, "Previous ProgramPoint: " << dumpToString(UserProgramPoint->TheInstruction, MST)); const AvailableSet &Available = AvailableExpressions.at(UserProgramPoint) .OutValue; return findAvailableRange(Available, I); } auto NextProgramPointIt = NextProgramPointInBlock.find(Where); if (NextProgramPointIt != NextProgramPointInBlock.end()) { revng_log(Log, "is before first ProgramPoint in BasicBlock"); ProgramPointNode *UserProgramPoint = NextProgramPointIt->second; revng_log(Log, "first ProgramPoint in BasicBlock: " << dumpToString(UserProgramPoint->TheInstruction, MST)); const AvailableSet &Available = AvailableExpressions.at(UserProgramPoint) .InValue; return findAvailableRange(Available, I); } revng_abort(); } auto getAvailableAt(Instruction *I, const Use &U) const { const auto *UserInstruction = cast(U.getUser()); return getAvailableAt(I, UserInstruction); } bool isAvailableAt(Instruction *I, const Instruction *Where) const { bool Result = not getAvailableAt(I, Where).empty(); revng_log(Log, "Result: " << Result); return Result; } bool isAvailableAt(Instruction *I, const Use &U) const { const auto *UserInstruction = cast(U.getUser()); return isAvailableAt(I, UserInstruction); } }; template using AEResult = AvailableExpressionsResult; template static AEResult getAvailableExpressions(Function &F, AliasAnalysis *AA, ModuleSlotTracker &MST) { revng_log(Log, "getAvailableExpressions: " << F.getName()); using AvailableExpression = AvailableExpression; using AvailableSet = AvailableSet; using AssignType = AssignType; auto Result = AEResult::makeFromFunction(F, MST); AvailableSet Bottom; for (ProgramPointNode *N : llvm::nodes(&Result.ProgramPointsGraph)) { Instruction *I = N->TheInstruction; if (mayReadMemory(I)) { Bottom.insert(AvailableExpression{ .Expression = I, .Assignment = nullptr, }); } auto *StoredOperand = getStoreValueOperand(I); auto *AssignedInstruction = dyn_cast_or_null(StoredOperand); if (AssignedInstruction) { Bottom.insert(AvailableExpression{ .Expression = AssignedInstruction, .Assignment = cast(I), }); } } ProgramPointsCFG *Graph = &Result.ProgramPointsGraph; ProgramPointNode *Entry = Graph->getEntryNode(); using AEMFP = AEMFP; AEMFP AvailableExpressionsMF{ AA, MST }; std::vector Entries = { Entry }; mfp::MFPConfiguration Configuration{ .Instance = &AvailableExpressionsMF, .Flow = Graph, .Bottom = &Bottom, .ExtremalLabels = &Entries, .EntryLabels = &Entries }; // std::exchange here is only needed to make revng check-conventions happy. std::exchange(Result.AvailableExpressions, mfp::getMaximalFixedPoint(Configuration)); return Result; } template struct PickedInstructions { SetVector ToSerialize = {}; MapVector *> ToReplaceWithAvailable = {}; // TODO: This is workaround for some limitations of legacy mode. It should be // unused in non-legacy mode, and can be removed when we drop legacy mode. SmallPtrSet *, 8> AssignToRemove = {}; }; // LLVM doesn't ship a function-level analysis that produces a // ModuleSlotTracker, so we add one here. The Result owns the MST through a // std::unique_ptr because ModuleSlotTracker is neither copyable nor movable // (its copy is deleted via a unique_ptr member, and its user-declared virtual // destructor suppresses the implicit move), and the FAM cache requires the // Result to be movable. class ModuleSlotTrackerAnalysis : public llvm::AnalysisInfoMixin { friend llvm::AnalysisInfoMixin; static llvm::AnalysisKey Key; public: struct Result { std::unique_ptr MST; }; Result run(llvm::Function &F, llvm::FunctionAnalysisManager &) { Result R{ std::make_unique(F.getParent(), /* InitMetadata = */ false) }; R.MST->incorporateFunction(F); return R; } }; llvm::AnalysisKey ModuleSlotTrackerAnalysis::Key = {}; template class AvailableExpressionsAnalysis : public llvm::AnalysisInfoMixin> { friend llvm::AnalysisInfoMixin>; static llvm::AnalysisKey Key; public: using Result = AvailableExpressionsResult; Result run(llvm::Function &F, llvm::FunctionAnalysisManager &FAM) { AliasAnalysis *AA = IsLegacy ? nullptr : &FAM.getResult(F); auto &MST = *FAM.getResult(F).MST; return getAvailableExpressions(F, AA, MST); } }; template<> AnalysisKey AvailableExpressionsAnalysis::Key = {}; template<> AnalysisKey AvailableExpressionsAnalysis::Key = {}; template using AEA = AvailableExpressionsAnalysis; template class InstructionToSerializePicker : public AnalysisInfoMixin> { friend llvm::AnalysisInfoMixin>; static llvm::AnalysisKey Key; public: using Result = PickedInstructions; using AvailableExpression = AvailableExpression; using AssignType = AssignType; private: const AEResult *AvailableExpressions = nullptr; std::unordered_map ProgramOrdering = {}; Result Picked; AliasAnalysis *AA; // A pointer (rather than a reference) so the picker can be default // constructed by the FAM factory; the MST is fetched and assigned in run(). ModuleSlotTracker *MST = nullptr; public: InstructionToSerializePicker() : AvailableExpressions(nullptr), ProgramOrdering() {} public: Result run(llvm::Function &F, llvm::FunctionAnalysisManager &FAM) { if constexpr (not IsLegacy) { AA = &FAM.getResult(F); } MST = FAM.getResult(F).MST.get(); AvailableExpressions = &FAM.getResult>(F); Picked = {}; ProgramOrdering = {}; return pick(F); } private: bool isSerializable(const Instruction &I) const { const Type *T = I.getType(); if constexpr (IsLegacy) { return not T->isVoidTy() and not T->isAggregateType() and not isCallToTagged(&I, FunctionTags::IsRef); } else { return not T->isVoidTy(); } } void pick(Instruction *I) { LoggerIndent Indent{ Log }; revng_log(Log, "pick(I), I: " << dumpToString(I, *MST)); Picked.ToSerialize.insert(I); }; bool isPicked(Instruction *I) const { return Picked.ToSerialize.contains(I); } Result pick(Function &F) { revng_log(Log, "pick: " << F.getName().str()); LoggerIndent Indent{ Log }; // Visit in RPO for determinism const auto RPO = llvm::ReversePostOrderTraversal(&F); size_t NextOrder = 0; for (BasicBlock *BB : RPO) for (Instruction &I : *BB) ProgramOrdering[&I] = NextOrder++; // First, pick all the statements amenable for serialization // Also compute the program order of instructions. if constexpr (IsLegacy) { revng_log(Log, "pick for serialization instructions with mayHaveSideEffects: " << F.getName().str()); LoggerIndent MoreIndent{ Log }; for (BasicBlock *BB : RPO) { for (Instruction &I : *BB) { // If it's not a statement, don't pick it. if (not mayHaveSideEffects(&I)) continue; // If it's a statement but it's not serializable, don't pick it. if (not isSerializable(I)) continue; // If I is a call that reads and writes memory, but has only one use, // we may want to not serialize it and inline it in the use. This will // probably happen often. Any such call can be modeled as a write // followed by a read, such that the write aliases everything except // local variables that don't escape. Inlining it would have the // effect of postponing a write, which is something that we haven't // ever considered to do. pick(&I); } } } // Then, start from memory reads, and traverse the dataflow to pick other // instructions that need to be serialized. for (BasicBlock *BB : RPO) for (Instruction &I : *BB) if (mayReadMemory(&I)) pickInstructionForMemoryRead(&I, &I); return Picked; } RecursiveCoroutine pickInstructionForMemoryRead(Instruction *I, Instruction *MemoryRead) { revng_log(Log, "PickFrom I: " << dumpToString(I, *MST)); revng_log(Log, "MemoryRead: " << dumpToString(MemoryRead, *MST)); LoggerIndent Indent{ Log }; // If I has already been picked we'de done. if (isPicked(I)) { revng_log(Log, "I isPicked"); rc_return; } if constexpr (IsLegacy) { // If it has side effects, we must have already picked it, unless it's not // serializable. Just return. if (mayHaveSideEffects(I)) { revng_log(Log, "mayHaveSideEffects(I)"); revng_assert(not isSerializable(*I)); rc_return; } revng_log(Log, "not mayHaveSideEffects(I)"); } else { // If MemoryRead may have side effects, it means that it's a call that // accesses memory in read+write fashion. If that happens, in principle we // don't have a strong case for picking I. However, if I has more than one // use, and we don't pick I, the MemoryRead (which is a call) will end up // duplicated in the full expression for I, which is not guaranteed to // break semantic. So, whenever MemoryRead may have side effects and I has // more than a single use, we have to pick I for preserving semantic. if (mayHaveSideEffects(MemoryRead) and I->getNumUses() > 1) { revng_log(Log, "I may have side effects, and has many uses"); pick(I); rc_return; } } revng_log(Log, "Check users"); LoggerIndent UserIndent{ Log }; MapVector ToReplaceWithAvailable; SmallVector UsesToRecurOn; // TODO: This is workaround for some limitations of legacy mode. It should // be unused in non-legacy mode, and can be removed when we drop legacy mode SmallPtrSet AssignToRemove; // If we're here, I hasn't been picked for serialization yet. // If possible we want to avoid picking it, because our broader goal is to // serialize as few instructions as possible. // // We can avoid serializing I if one of the following applies. // // 1. // MemoryRead is available directly in all uses of I. In this case I can be // emitted inline in each of its uses, and MemoryRead will be inlined along // with I in every use of I, without breaking semantics. // We can avoid serializing I, and recur. // // 2. // For each use U of I where MemoryRead is not available directly, // MemoryRead is available indirectly via some assignment S that stored it // somewhere. // In this case, for each of such use U, we want to make sure that if we // inline I in U we will not use MemoryRead directly, but replace the // transitive use of MemoryRead in I with a load from the same location as // S. // However, in practice we have a guarantee that this holds, thanks to // recursion. Indeed, if we're reached this place via recursion we must have // already processed the transitive use of MemoryRead in I, and we have // solved that part of the problem earlier. This means can be sure that // whenever we emit I, if MemoryRead is not available directly at I but only // via an assignment S, we have already set up things so that we will use a // load from S instead of MemoryRead directly to emit I. // So we can avoid serializing I, and recur. // // 3. If MemoryRead is not available at U we check if I itself is available // at U via some assignment S that assigned it to some location, where the // value of I is still available. If I it's not available via any assignment // we have to bail out and pick I for serialization. // // 4. For each use U of I where MemoryRead is not available, I happens to be // available via some assignment S that assigned it to some location, where // the value of I is still available. // // If we're looking at how I is used in U, it means that we've already // processed how MemoryRead is used in I, hence by construction the // expression that will be emitted in S to compute the value of I will // already use the correct value of MemoryRead, either directly or from // another assignment T that assigns before I and makes it available // indirectly at I. // // As a result, we just mark the use U to be replaced with the value of I // available at S. // For each U Use of I where MemoryRead is not available, check if the // whole expression represented by I is available at U. If so add it to // the ToReplaceWithAvailable. // Otherwise if we find even a single use of I where MemoryRead is not // available and such that I itself is not available, we have to require I // to be either be available somewhere else or be serialized in a new local // variable. for (Use &U : I->uses()) { auto *User = cast(U.getUser()); revng_log(Log, "UseNo: " << U.getOperandNo() << " User: " << dumpToString(User, *MST)); LoggerIndent MoreUserIndent{ Log }; // U of I could have already been picked in a previous iteration, from a // different memory read. // Detect that case and quickly bail out, because in practice we have // already taken that decision and we will reuse that. // No need to recur here. if (Picked.ToReplaceWithAvailable.count(&U)) { revng_log(Log, "I was already picked and it's available at User, reading " "from: " << dumpToString(Picked.ToReplaceWithAvailable.lookup(&U), *MST)); continue; } if constexpr (IsLegacy) { // Skip over the Assign operand representing target variables for // assignments, because in legacy mode we need to preserve them. // This is a workaround and is not semantic preserving. // It will be dropped in non-legacy mode. if (isStorePointerOperand(U, User)) { revng_log(Log, "isStorePointerOperand(U, User)"); // In principle we should do: // UsesToRecurOn.push_back(&U); // But that would just do nothing because Store instructions can't // have any Use. So we can just continue here. continue; } } revng_log(Log, "Find where I is available"); // Case 1. and 2. of the description above. // If the MemoryRead is available in U either directly or via an // assignment S we're fine and we start looking at the next use. if (AvailableExpressions->isAvailableAt(MemoryRead, U)) { revng_log(Log, "isAvailableAt(MemoryRead, User)"); UsesToRecurOn.push_back(&U); continue; } revng_log(Log, "not isAvailableAt(MemoryRead, User)"); // Case 3. If selectAssignment fails I is not available at U via any // assignment, so we pick I and bail out.. AssignType *SelectedAssign = selectAssignment(I, U); if (not SelectedAssign) { revng_log(Log, "SelectedAssign: nullptr"); revng_log(Log, "I is not available at User via other assignments"); rc_return pick(I); } revng_log(Log, "SelectedAssign: " << dumpToString(SelectedAssign, *MST)); // Case 4. // If the user is not writing to memory, it cannot interact in any way // with the memory written to by SelectedAssign. // Just mark U to be replaced from a read from the location assigned by // SelectedAssign. if (not mayWriteToMemory(User) or not IsLegacy) { ToReplaceWithAvailable[&U] = SelectedAssign; revng_log(Log, "not mayWriteToMemory(User)"); // We don't need to recur on uses of U, because all of them will // effectively be replaced by reads from SelectedAssign, so the // MemoryRead will not be affecting them anymore. continue; } else if constexpr (IsLegacy) { // TODO: This is workaround for some limitations of legacy mode. It // should be unused in non-legacy mode, and can be removed when we drop // legacy mode. // // The following is a workaround to avoid emitting self-assigments in C, // which are perfectly fine semantically but ugly to see. revng_log(Log, "IsLegacy and mayWriteToMemory(User)"); // If User is an Assign, and it assigns the same local variable as the // SelectedAssign, replacing U with a Copy from the LocalVar assigned by // SelectedAssign from Load from the variable that assigns the same // local variable as the SelectedAssign, would turn User into a self // assignment. if (auto *UserAssign = getCallToTagged(User, FunctionTags::Assign); UserAssign and legacyLocalVariablesNoAlias(UserAssign, SelectedAssign)) { AssignToRemove.insert(UserAssign); // In principle we should recur on this but Assign is always // guaranteed to have zero uses. } else { // In all the other cases it's fine to replace U with a Copy from the // LocalVar assigned by SelectedAssign ToReplaceWithAvailable[&U] = SelectedAssign; } } } // If we reach this point, it means that no user forced us to serialize I. // At this point we can commit ToReplaceWithAvailable into // Picked.ToReplaceWithAvailable. for (const auto &Element : ToReplaceWithAvailable) Picked.ToReplaceWithAvailable.insert(Element); // And we can also commit the fact that we want to remove the Assign. // TODO: This is workaround for some limitations of legacy mode. It // should be unused in non-legacy mode, and can be removed when we drop // legacy mode. if constexpr (IsLegacy) for (const auto &Assign : AssignToRemove) Picked.AssignToRemove.insert(Assign); // If we reach this point I is has not been picked for serialization, and // MemoryRead is available to all users of I, either directly of via some // other local variable where the whole I is available. // Hence, we can safely decide that I will not be picked. // We still have to check all users of I that are using MemoryRead directly // (transitively via I). Those may still be picked. revng_log(Log, "Recur on users of I"); for (Use *U : UsesToRecurOn) { auto *User = cast(U->getUser()); LoggerIndent UserIndent{ Log }; revng_log(Log, "UseNo: " << U->getOperandNo() << " User: " << dumpToString(User, *MST)); LoggerIndent MoreUserIndent{ Log }; rc_recur pickInstructionForMemoryRead(User, MemoryRead); } rc_return; } /// Returns an assignment where I is available at U AssignType *selectAssignment(Instruction *I, Use &U) { // If the MemoryRead is not available at U, it may still be the case that // I itself is available at U, because it was stored into a pre-existing // alloca. auto Available = AvailableExpressions->getAvailableAt(I, U); if (Available.empty()) { revng_log(Log, "I is not available at User"); return nullptr; } revng_log(Log, "I is available at User"); if (Log.isEnabled()) { LoggerIndent Indent{ Log }; for (const AvailableExpression &AE : Available) { revng_log(Log, "AE.Available = " << dumpToString(AE.Expression, *MST)); revng_log(Log, "AE.Assignment = " << dumpToString(AE.Assignment, *MST)); } } // Here we have a bunch of places where I is available in U. // We want to pick one, so that we will end up replacing the use of I in U // with a load from there. SmallVector AssignsWhereIIsAvailable; llvm::copy(Available | std::views::transform([](const AvailableExpression &A) { return A.Assignment; }) | std::views::filter([](const AssignType *A) { return A != nullptr; }), std::back_inserter(AssignsWhereIIsAvailable)); if (AssignsWhereIIsAvailable.empty()) return nullptr; // Sort them in program order. // This is not strictly necessary, but it ensures determinism in picking the // candidate. // TODO: is this better or worse as an heuristic as opposed to reverse // program order, or possibly other heuristics? llvm::sort(AssignsWhereIIsAvailable, [&PO = ProgramOrdering](const AssignType *LHS, const AssignType *RHS) { return PO.at(LHS) < PO.at(RHS); }); AssignType *CandidateAssign = AssignsWhereIIsAvailable.front(); revng_log(Log, "First CandidateAssign: " << dumpToString(CandidateAssign, *MST)); return CandidateAssign; } }; template<> AnalysisKey InstructionToSerializePicker::Key = {}; template<> AnalysisKey InstructionToSerializePicker::Key = {}; using TypeMap = std::map; template using LVB = LocalVariableBuilder; template LocalVariableBuilder makeVariableBuilder(Function &F, unsigned InputPointerByteSize, const model::Binary &Model) { if constexpr (IsLegacy) { return LVB::makeLegacy(Model, &F); } else { VariableBuilderTypes Types = VariableBuilderTypes{ *F.getParent(), InputPointerByteSize }; return LVB::make(Types, &F); } } template class VariableInserter { public: using PickedInstructions = PickedInstructions; private: // The Model is only used in legacy mode. Drop this when we drop legacy mode. const model::Binary &Model; // The TypeMap is only used in legacy mode. Drop this when we drop legacy // mode. const TypeMap TheTypeMap; Function &F; // TODO: remove when we drop legacy mode LocalVariableBuilder VariableBuilder; public: VariableInserter(Function &TheF, unsigned InputPointerByteSize, // TODO: drop the next 2 arguments when we drop legacy mode const model::Binary &TheModel, TypeMap &&TMap) : Model(TheModel), TheTypeMap(std::move(TMap)), F(TheF), VariableBuilder(makeVariableBuilder(F, InputPointerByteSize, TheModel)) {} public: bool run(const PickedInstructions &Picked) { bool Changed = false; for (const auto &[TheUse, TheAssign] : Picked.ToReplaceWithAvailable) TheUse->set(createCopyFromAssignedOnUse(TheAssign, *TheUse)); // TODO: This is workaround for some limitations of legacy mode. It // should be unused in non-legacy mode, and can be removed when we drop // legacy mode. if constexpr (IsLegacy) { for (Instruction *I : Picked.AssignToRemove) { Changed = true; I->eraseFromParent(); } } for (Instruction *I : Picked.ToSerialize) { // If ToSerialize contains something with 0 uses we don't add the // local variable. The Clifter will identify this a statement because it // has zero uses, and it will turn it into an ExpressionStatement in // place, preserving the ordering as if the local variable was emitted. if (IsLegacy or I->getNumUses() > 0) Changed |= serializeToLocalVariable(I); } return Changed; } private: bool serializeToLocalVariable(Instruction *I); bool shouldReplaceUseWithCopies(const Use &U) const; // TODO: make this const when we drop legacy mode LocalVarType *createLocalVariableFor(Instruction *I) { // TODO: remove when we drop legacy mode. DebugLoc DL = I->getDebugLoc(); if constexpr (not IsLegacy) { revng::NonDebugInfoCheckingIRBuilder B(F.getContext()); B.SetInsertPointPastAllocas(&F, DL); return B.createSimpleAlloca(I->getType()); } else { revng_assert(I->getType()->isIntOrPtrTy()); const model::UpcastableType &VariableType = TheTypeMap.at(I); auto *LocalVariable = VariableBuilder.createLocalVariable(*VariableType); LocalVariable->setDebugLoc(DL); return LocalVariable; } } // TODO: make this const when we drop legacy mode CopyType *createCopyOnUse(Value *ToCopy, Use &U) { if constexpr (not IsLegacy) { // Create a copy from the assigned location at the proper insertion point. auto *InsertBefore = cast(U.getUser()); DebugLoc DL = InsertBefore->getDebugLoc(); if (auto *I = dyn_cast(ToCopy)) DL = I->getDebugLoc(); revng::NonDebugInfoCheckingIRBuilder B(InsertBefore, DL); if (auto *Alloca = dyn_cast(ToCopy)) return B.createLoadFromVariable(Alloca, U->getType()); return B.CreateLoad(U->getType(), ToCopy); } else { // TODO: remove when we drop legacy mode. // // We have to cast this to a CallInst, because in legacy mode we can only // make copies from things that have reference semantics, that are // represented in LLVM ir with special calls to custom opcodes. CopyType *Call = cast(ToCopy); // In some cases we don't need to copy it. if (shouldReplaceUseWithCopies(U)) Call = VariableBuilder.createCopyOnUse(Call, U); return Call; } } // TODO: make this const when we drop legacy mode CopyType *createCopyFromAssignedOnUse(AssignType *Store, Use &U) { if constexpr (not IsLegacy) { return createCopyOnUse(Store->getPointerOperand(), U); } else { // TODO: remove when we drop legacy mode. return VariableBuilder.createCopyFromAssignedOnUse(Store, U); } } // TODO: make this const when we drop legacy mode AssignType *createAssignment(LocalVarType *LocalVariable, Instruction *ValueToAssign) { auto NextInstruction = ValueToAssign->getNextNonDebugInstruction(); if constexpr (not IsLegacy) { revng::NonDebugInfoCheckingIRBuilder B(NextInstruction, ValueToAssign->getDebugLoc()); return B.createStoreToVariable(ValueToAssign, LocalVariable); } else { // TODO: drop when we drop legacy mode. return VariableBuilder.createAssignmentBefore(LocalVariable, ValueToAssign, NextInstruction); } } }; template using VI = VariableInserter; template bool VI::shouldReplaceUseWithCopies(const Use &U) const { if constexpr (not IsLegacy) { return true; } else { const auto *I = cast(U.get()); auto *Call = getCallToIsolatedFunction(I); if (not Call) return true; const auto *ProtoT = getCallSitePrototype(Model, cast(I)); abi::FunctionType::Layout Layout = abi::FunctionType::Layout::make(*ProtoT); // If the Isolated function doesn't return an aggregate, we have to // inject copies from local variables. using namespace abi::FunctionType; if (Layout.returnMethod() != ReturnMethod::ModelAggregate) return true; // SPTAR return aggregates also need copies from local variables, // because they are emitted as scalar pointer variables in C. if (Layout.hasSPTAR()) return true; // Non-SPTAR return aggregates, in legacy mode, are special in many ways: // 1. they basically imply a LocalVariable; // 2. their only expected use is supposed to be in custom opcodes that // expect references // For these reasons it would be wrong to inject a Copy. if (isCallToTagged(U.getUser(), FunctionTags::Assign)) { // If it's an assignment, and the operand number is 0, the value of I is // being written somewhere (in the location referenced by operand 1). // Hence we have to inject a copy. return U.getOperandNo() == 0; } revng_assert(1 == U.getOperandNo()); revng_assert(isCallToTagged(U.getUser(), FunctionTags::AddressOf) or isCallToTagged(U.getUser(), FunctionTags::ModelGEPRef)); return false; } } template bool VI::serializeToLocalVariable(Instruction *I) { // We can't serialize instructions with reference semantics into local // variables because C doesn't have references. revng_assert(not isCallToTagged(I, FunctionTags::IsRef)); // First, we have to declare the LocalVariable, always at the entry block. // Create instruction that allocates a LocalVariable LocalVarType *LocalVariable = createLocalVariableFor(I); // Then, we have to replace all the uses of I so that they make a Copy // from the LocalVariable, unless it's a call to an IsolatedFunction that // already returns a local variable, in which case we don't have to do // anything with uses. for (Use &U : llvm::make_early_inc_range(I->uses())) U.set(createCopyOnUse(LocalVariable, U)); createAssignment(LocalVariable, I); return true; } static void registerAliasAnalysis(FunctionAnalysisManager &FAM) { PassBuilder PB; PB.registerFunctionAnalyses(FAM); FAM.registerPass([] { return llvm::registerAAAnalyses(); }); } template static void registerCommonAnalyses(FunctionAnalysisManager &FAM) { FAM.registerPass([] { return ModuleSlotTrackerAnalysis(); }); FAM.registerPass([] { return AvailableExpressionsAnalysis(); }); FAM.registerPass([] { return InstructionToSerializePicker(); }); } template class SwitchToStatements : public llvm::PassInfoMixin> { private: // This is only used in legacy mode for operating the LocalVariableBuilder // inside the VariableInserter. // TODO: drop when we drop legacy mode. const model::Binary *Model; /// The size in bytes of a pointer in the Binary we're decompiling. /// Necessary for initializing a LocalVariableBuilder without the Model, in /// non-legacy mode. unsigned InputPointerByteSize; public: // This is meant to be used only in non-legacy mode. SwitchToStatements(unsigned InputPointerByteSize) : Model(nullptr), InputPointerByteSize(InputPointerByteSize) { revng_assert(not IsLegacy); } private: // This is meant to be used only in legacy mode. // This is why it's private, so we it can only be invoked via the factory, // which is only available when IsLegacy is true. // // TODO: drop this when we drop legacy mode. SwitchToStatements(const model::Binary &TheModel) : Model(&TheModel), InputPointerByteSize(getPointerSize(Model->Architecture())) { revng_assert(IsLegacy); } public: // Factory meant to be called only for legacy mode. // // TODO: drop this when we drop legacy mode. static SwitchToStatements makeLegacy(const model::Binary &Model) requires IsLegacy { return SwitchToStatements(Model); } public: llvm::PreservedAnalyses run(llvm::Function &F, llvm::FunctionAnalysisManager &FAM) { TypeMap InstructionTypes = {}; if constexpr (IsLegacy) { auto ModelFunction = llvmToModelFunction(*Model, F); revng_assert(ModelFunction != nullptr); InstructionTypes = initModelTypesConsideringUses(F, ModelFunction, *Model, /* PointersOnly */ false); } VariableInserter VarInserter{ F, InputPointerByteSize, *Model, std::move(InstructionTypes) }; const auto &Picked = FAM.getResult>(F); bool Changed = VarInserter.run(Picked); return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all(); } static void registerCallbacks(PassBuilder &PB) requires(not IsLegacy) { using PipelineElementArray = ArrayRef; PB.registerAnalysisRegistrationCallback(registerAliasAnalysis); PB.registerAnalysisRegistrationCallback(registerCommonAnalyses); PB.registerPipelineParsingCallback([](StringRef Name, FunctionPassManager &FPM, PipelineElementArray) { if (Name == "switch-to-statements-test") { FPM.addPass(SwitchToStatements{ /*InputPointerSize*/ 8 }); return true; } return false; }); } }; extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK llvmGetPassPluginInfo() { return { LLVM_PLUGIN_API_VERSION, "SwitchToStatementsTests", "1.0", SwitchToStatements::registerCallbacks }; } // The template is only for legacy mode. // TODO: drop when we drop legacy mode. template bool switchToStatements(const model::Binary *Model, llvm::Function &F); // This specialization is only for legacy mode. // TODO: drop when we drop legacy mode. template<> bool switchToStatements(const model::Binary *Model, llvm::Function &F) { revng_log(Log, "switchToStatements (legacy): " << F.getName()); FunctionAnalysisManager FAM; // FPM.run queries some standard LLVM analyses unconditionally (e.g. // PassInstrumentationAnalysis), so we need to register them on the FAM. PassBuilder PB; PB.registerFunctionAnalyses(FAM); registerCommonAnalyses(FAM); FunctionPassManager FPM; FPM.addPass(SwitchToStatements::makeLegacy(*Model)); llvm::PreservedAnalyses Preserved = FPM.run(F, FAM); return Preserved.areAllPreserved() ? false : true; } template<> bool switchToStatements(const model::Binary *Model, llvm::Function &F) { revng_log(Log, "switchToStatements (non-legacy): " << F.getName()); ModuleAnalysisManager MAM; FunctionAnalysisManager FAM; // Cross register passes, because function-level alias analyses fall back to // querying GlobalsAA in some cases. If the analysis manager aren't // cross-registered, that fallback query just crashes. // We don't activate GlobalsAA, but GlobalsAA still needs to be registered, in // order for local AA to query it and see it doesn't have cached results // because it did not run. FAM.registerPass([&MAM] { return ModuleAnalysisManagerFunctionProxy(MAM); }); MAM.registerPass([&FAM] { return FunctionAnalysisManagerModuleProxy(FAM); }); // Register module-level analyses, because alias analysis looks up GlobalsAA, // which is a module-level pass. PassBuilder PB; PB.registerModuleAnalyses(MAM); // Register the standard LLVM function analyses (PassInstrumentationAnalysis, // ...) and the alias analyses we use further down. registerAliasAnalysis(FAM); registerCommonAnalyses(FAM); FunctionPassManager FPM; FPM.addPass(SwitchToStatements(getPointerSize(Model->Architecture()))); llvm::PreservedAnalyses Preserved = FPM.run(F, FAM); return Preserved.areAllPreserved() ? false : true; } template class SwitchToStatementsPass : public FunctionPass { public: static char ID; SwitchToStatementsPass() : FunctionPass(ID) {} void getAnalysisUsage(AnalysisUsage &AU) const override { AU.setPreservesCFG(); AU.addRequired(); } bool runOnFunction(Function &F) override; }; template<> char SwitchToStatementsPass::ID = 0; template class SwitchToStatementsPass; template<> char SwitchToStatementsPass::ID = 0; template class SwitchToStatementsPass; template bool SwitchToStatementsPass::runOnFunction(llvm::Function &F) { auto *Model = getAnalysis().get().getReadOnlyModel().get(); return switchToStatements(Model, F); } using RegisterLegacy = RegisterPass>; static RegisterLegacy X("legacy-switch-to-statements", "LegacySwitchToStatements", false, false); using Register = RegisterPass>; static Register Y("switch-to-statements", "SwitchToStatementsPass", false, false); namespace revng::pypeline::piperuns { // TODO: inline switchToStatements once we dismiss the old pipeline void SwitchToStatements::runOnLLVMFunction(const model::Function &Function, llvm::Function &LLVMFunction) { switchToStatements(Model.get(), LLVMFunction); } } // namespace revng::pypeline::piperuns