// // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include #include #include #include #include #include #include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SetVector.h" #include "llvm/ADT/iterator_range.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/CFG.h" #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/Instruction.h" #include "llvm/Pass.h" #include "revng/ABI/FunctionType/Layout.h" #include "revng/ABI/ModelHelpers.h" #include "revng/ADT/GenericGraph.h" #include "revng/ADT/SmallMap.h" #include "revng/InitModelTypes/InitModelTypes.h" #include "revng/MFP/MFP.h" #include "revng/MFP/SetLattices.h" #include "revng/Model/Binary.h" #include "revng/Model/IRHelpers.h" #include "revng/Model/LoadModelPass.h" #include "revng/Support/Debug.h" #include "revng/Support/DecompilationHelpers.h" #include "revng/Support/FunctionTags.h" static Logger<> Log{ "switch-to-statements" }; using namespace llvm; struct SwitchToStatements : public FunctionPass { public: static char ID; SwitchToStatements() : FunctionPass(ID) {} void getAnalysisUsage(AnalysisUsage &AU) const override { AU.setPreservesCFG(); AU.addRequired(); } bool runOnFunction(Function &F) override; }; struct AvailableExpression { // The expression that is available Instruction *Expression = nullptr; // The Assign call 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. // We need to assign a semantic to nullptr for CallInst and Copy, which are // note necessarily assigned to any location by an Assign call. CallInst *Assign = nullptr; bool operator==(const AvailableExpression &) const = default; std::strong_ordering operator<=>(const AvailableExpression &) const = default; }; using AvailableSet = std::set; constexpr size_t SmallSize = 8; using InstructionVector = SmallVector; using InstructionSetVector = SmallSetVector; struct ProgramPointData { Instruction *TheInstruction = nullptr; ProgramPointData(Instruction *I) : TheInstruction(I){}; }; static auto findAvailableRange(const AvailableSet &Availables, Instruction *I) { auto Begin = Availables.lower_bound(AvailableExpression{ .Expression = I, .Assign = nullptr }); auto End = Availables.upper_bound(AvailableExpression{ .Expression = std::next(I), .Assign = nullptr }); return llvm::make_range(Begin, End); } using ProgramPointNode = BidirectionalNode; using ProgramPointsCFG = GenericGraph; struct AvailableExpressionsAnalysis; using ALA = AvailableExpressionsAnalysis; struct AvailableExpressionsAnalysis { using GraphType = ProgramPointsCFG *; using LatticeElement = AvailableSet; using Label = ProgramPointNode *; using MFPResult = MFP::MFPResult; ALA::LatticeElement combineValues(const ALA::LatticeElement &LHS, const ALA::LatticeElement &RHS) const { return SetIntersectionLattice::combineValues(LHS, RHS); } bool isLessOrEqual(const ALA::LatticeElement &LHS, const ALA::LatticeElement &RHS) const { return SetIntersectionLattice::isLessOrEqual(LHS, RHS); } ALA::LatticeElement applyTransferFunction(ProgramPointNode *L, const ALA::LatticeElement &E) const; }; using LatticeElement = ALA::LatticeElement; using MFPResult = ALA::MFPResult; using ResultMap = std::map; static bool isStatement(const Instruction *I) { // TODO: this is workaround for SelectInst being often involved in nasty // huge dataflows. // In the future we should drop this from here and add a separate pass after // this, that takes care of forcing local variables for nasty dataflows. if (isa(I)) return true; return hasSideEffects(*I); } static bool isProgramPoint(const Instruction *I) { // TODO: In the future this pass will have to be updated to handle // Load/Store/Alloca instead of Copy/Assign/LocalVariable, in order to be // able to use LLVM's alias analysis. // For now we just assume that we don't have Load/Store/Alloca at all. // Whenever we'll do the switchover, we'll have to replace all the logic // of Copy/Assign/LocalVariable with Load/Store/Alloca, and just drop // everything related to Copy/Assign/LocalVariable. // PHINodes will have to be dealt with if/when we move this pass before // ExitSSA. revng_assert(not isa(I) and not isa(I) and not isa(I) and not isa(I)); return I == &I->getParent()->front() or isStatement(I) or mayReadMemory(*I); } 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; } static std::optional getAccessedLocalVariable(const Instruction *I) { // If it's not a Copy not an Assign then it's not an access to a local // variable. const CallInst *CallToCopy = getCallToTagged(I, FunctionTags::Copy); const CallInst *CallToAssign = getCallToTagged(I, FunctionTags::Assign); if (not CallToCopy and not CallToAssign) return std::nullopt; const CallInst *AccessCall = CallToCopy ? CallToCopy : CallToAssign; unsigned AccessArgumentNumber = CallToAssign ? 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); } static bool localVariablesNoAlias(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 = getAccessedLocalVariable(I); std::optional MayBeAccessedByJ = getAccessedLocalVariable(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; // For all the other cases they are noAlias only if the accessed // local variable is different. return AccessedByI != AccessedByJ; } static bool doesNotAccessMemory(const Instruction *I) { auto *Call = dyn_cast_or_null(I); return Call and Call->getMemoryEffects().doesNotAccessMemory(); } static bool noAlias(const Instruction *I, const Instruction *J) { revng_log(Log, "noAlias?"); LoggerIndent X{ Log }; revng_log(Log, "I: " << dumpToString(I)); revng_log(Log, "J: " << dumpToString(J)); LoggerIndent XX{ Log }; // If either instruction doesn't access memory, they are noAlias for sure. if (doesNotAccessMemory(I) or doesNotAccessMemory(J)) { revng_log(Log, "I or J doesNotAccessMemory"); return true; } // Here both instructions access memory. // First, 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 if (localVariablesNoAlias(I, J)) { revng_log(Log, "I and J both access local variables that do not alias"); return true; } // TODO: In all the other cases, to reason accurately about aliasing, we would // need LLVM's alias analysis. At the moment this is out of scope, so we // always fall back to false, meaning that we can't say for sure that I and J // do not alias. revng_log(Log, "I and J aren't provably noAlias"); return false; } static void applyTransferFunction(Instruction *I, LatticeElement &E) { revng_log(Log, "applyTransferFunction on Instruction: " << dumpToString(I)); LoggerIndent X{ Log }; if (isStatement(I)) { revng_log(Log, "isStatement"); LoggerIndent XX{ Log }; for (const AvailableExpression &A : llvm::make_early_inc_range(E)) { const auto &[Available, Assign] = A; revng_log(Log, "Available: " << dumpToString(Available)); revng_log(Log, "Assign: " << dumpToString(Assign)); LoggerIndent XXX{ Log }; if (not noAlias(I, Available)) { revng_log(Log, "Available: " << dumpToString(Available)); revng_log(Log, "is not noAlias (MayAlias) with I"); revng_log(Log, "erase Available"); E.erase(A); } else if (not noAlias(I, Assign)) { revng_log(Log, "Assign: " << dumpToString(Assign)); revng_log(Log, "erase Available"); E.erase(A); } else { revng_log(Log, "is noAlias with I"); } } } if (auto *Assign = getCallToTagged(I, FunctionTags::Assign)) { if (isa(Assign->getArgOperand(0))) { revng_log(Log, "I is Assign"); revng_log(Log, "insert Available: " << dumpToString(Assign->getArgOperand(0))); revng_log(Log, " Assign: " << dumpToString(Assign)); E.insert(AvailableExpression{ .Expression = cast(Assign->getArgOperand(0)), .Assign = Assign, }); } } if (mayReadMemory(*I)) { revng_log(Log, "mayReadMemory -> insert Available: I"); E.insert(AvailableExpression{ .Expression = I, .Assign = nullptr, }); } } LatticeElement ALA::applyTransferFunction(ProgramPointNode *ProgramPoint, const LatticeElement &E) const { Instruction *I = ProgramPoint->TheInstruction; revng_log(Log, "applyTransferFunction on ProgramPoint: " << dumpToString(I)); LoggerIndent Indent{ Log }; LatticeElement Result = E; revng_log(Log, "initial set"); if (Log.isEnabled()) { LoggerIndent X{ Log }; for (const auto &[Available, Assign] : Result) { revng_log(Log, "Available: " << dumpToString(Available)); revng_log(Log, "Assign: " << dumpToString(Assign)); } } ::applyTransferFunction(I, Result); revng_log(Log, "final set"); if (Log.isEnabled()) { LoggerIndent X{ Log }; for (const auto &[Available, Assign] : Result) { revng_log(Log, "Available: " << dumpToString(Available)); revng_log(Log, "Assign: " << dumpToString(Assign)); } } return Result; } 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. struct ProgramPointsGraphWithInstructionMap { ProgramPointsCFG ProgramPointsGraph; InstructionProgramPoint ProgramPoint; InstructionProgramPoint PreviousProgramPointInBlock; InstructionProgramPoint NextProgramPointInBlock; auto getAvailableAt(Instruction *I, const Instruction *Where, const ResultMap &MFPResultMap) const { revng_log(Log, "IsAvailableAt"); revng_log(Log, "I: " << dumpToString(I)); revng_log(Log, "Where: " << dumpToString(Where)); auto ProgramPointIt = ProgramPoint.find(Where); if (ProgramPointIt != ProgramPoint.end()) { revng_log(Log, "is ProgramPoint"); ProgramPointNode *UserProgramPoint = ProgramPointIt->second; const AvailableSet &Available = MFPResultMap.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)); const AvailableSet &Available = MFPResultMap.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)); const AvailableSet &Available = MFPResultMap.at(UserProgramPoint).InValue; return findAvailableRange(Available, I); } revng_abort(); } bool isAvailableAt(Instruction *I, const Instruction *Where, const ResultMap &MFPResultMap) const { bool Result = not getAvailableAt(I, Where, MFPResultMap).empty(); revng_log(Log, "Result: " << Result); return Result; } }; using PPGWithInstructionMap = ProgramPointsGraphWithInstructionMap; static PPGWithInstructionMap makeProgramPointsWithInstructionsGraph(Function &F) { SmallMap, 8> BlockToBeginEndNode; PPGWithInstructionMap Result; 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; } static ResultMap getMFP(ProgramPointsCFG *TheGraph) { AvailableSet Bottom; for (ProgramPointNode *N : llvm::nodes(TheGraph)) { Instruction *I = N->TheInstruction; if (mayReadMemory(*I)) { Bottom.insert(AvailableExpression{ .Expression = I, .Assign = nullptr, }); } if (auto *Assign = getCallToTagged(I, FunctionTags::Assign)) { if (isa(Assign->getArgOperand(0))) { Bottom.insert(AvailableExpression{ .Expression = cast(Assign->getArgOperand(0)), .Assign = Assign, }); } } } AvailableSet Empty{}; return MFP::getMaximalFixedPoint({}, TheGraph, Bottom, Empty, { TheGraph->getEntryNode() }); } struct PickedInstructions { SetVector ToSerialize = {}; MapVector ToReplaceWithAvailable = {}; SmallPtrSet AssignToRemove = {}; }; class InstructionToSerializePicker { public: InstructionToSerializePicker(Function &TheF, const ProgramPointsGraphWithInstructionMap &TheGraph, const ResultMap &TheMFPResult) : F(TheF), Graph(TheGraph), MFPResultMap(TheMFPResult), Picked() {} public: const PickedInstructions &pick() { Picked = {}; // Visit in RPO for determinism const auto RPO = llvm::ReversePostOrderTraversal(&F); // First, pick all the statements amenable for serialization // Also compute the program order of instructions. size_t NextOrder = 0; for (BasicBlock *BB : RPO) { for (Instruction &I : *BB) { if (isStatement(&I) and not I.getType()->isVoidTy() and not I.getType()->isAggregateType()) { revng_log(Log, "I: " << dumpToString(I)); revng_log(Log, "Picked.ToSerialize.insert(I)"); Picked.ToSerialize.insert(&I); } ProgramOrdering[&I] = NextOrder++; } } // 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)) pickFrom(&I, &I); return Picked; } private: RecursiveCoroutine shouldSerializeReadBeforeOrAtI(Instruction *I, Instruction *MemoryRead) { revng_log(Log, "PickFrom I: " << dumpToString(I)); revng_log(Log, "MemoryRead: " << dumpToString(MemoryRead)); LoggerIndent Indent{ Log }; auto *IType = I->getType(); // If I has already been picked for serialization it means that I shouldn't // be serialied for it. if (Picked.ToSerialize.contains(I)) { revng_log(Log, "Picked.ToSerialize.contains(I)"); rc_return false; } // If it's a statement we must have already picked it. Just return false. if (isStatement(I)) { revng_log(Log, "I isStatement"); if (not IType->isVoidTy() and not IType->isAggregateType()) { revng_assert(Picked.ToSerialize.contains(I)); } rc_return false; } // If I has no uses, we are done, and there's no reason to require the // serialization of MemoryRead before I. if (not I->getNumUses()) { revng_log(Log, "I has no uses"); rc_return false; } // If it exists a use U of I for which MemoryRead is not available, then // MemoryRead should be serialized before or at I, unless the whole // expression represented by I is available somewhere else. revng_log(Log, "Check users"); LoggerIndent UserIndent{ Log }; const auto IsMemoryReadAvailableAt = [this, MemoryRead](const Use &TheUse) { const auto *UserInstruction = cast(TheUse.getUser()); return Graph.isAvailableAt(MemoryRead, UserInstruction, MFPResultMap); }; const auto SerializeI = [I, IType = I->getType(), &ToSerialize = Picked.ToSerialize]() { if (not IType->isVoidTy() and not IType->isAggregateType() and not isCallToTagged(I, FunctionTags::IsRef)) { revng_log(Log, "Picked.ToSerialize.serialize(I), with I: " << dumpToString(I)); ToSerialize.insert(I); return false; } return true; }; MapVector ToReplaceWithAvailable; SmallPtrSet AssignToRemove; // 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 serialized in a new local variable. for (Use &U : I->uses()) { auto *UserInstruction = cast(U.getUser()); revng_log(Log, "User: " << dumpToString(UserInstruction)); LoggerIndent MoreUserIndent{ Log }; if (IsMemoryReadAvailableAt(U)) { revng_log(Log, "IsMemoryReadAvailableAt(User)"); continue; } revng_log(Log, "MemoryRead is not available in User"); auto *UserAssignCall = getCallToTagged(UserInstruction, FunctionTags::Assign); if (UserAssignCall) { // Skip over the Assign operand representing variables that are being // assigned, because we needwant to preserve them. if (UserAssignCall->isArgOperand(&U) and UserAssignCall->getArgOperandNo(&U) == 1) { continue; } } auto AvailableRange = Graph.getAvailableAt(I, UserInstruction, MFPResultMap); if (AvailableRange.empty()) { revng_log(Log, "Found unavailable use. Serialize I"); rc_return SerializeI(); } else { revng_log(Log, "But I is available"); if (auto It = Picked.ToReplaceWithAvailable.find(&U); It != Picked.ToReplaceWithAvailable.end()) { revng_log(Log, "I can be read from address: " << dumpToString(It->second)); } else { revng_log(Log, "Select first viable address in program order"); CallInst *Selected = nullptr; size_t ProgramOrder = std::numeric_limits::max(); for (const AvailableExpression &A : AvailableRange) { if (nullptr == A.Assign) continue; size_t NewProgramOrder = ProgramOrdering.at(A.Assign); if (NewProgramOrder < ProgramOrder) { ProgramOrder = NewProgramOrder; Selected = A.Assign; } } revng_log(Log, "Selected: " << dumpToString(Selected)); if (not Selected) { revng_log(Log, "Selected is not an assignment. Serialize I"); rc_return SerializeI(); } revng_assert(isCallToTagged(Selected, FunctionTags::Assign)); if (not UserAssignCall) { ToReplaceWithAvailable[&U] = Selected; continue; } std::optional MayBeAccessedByUser = getAccessedLocalVariable(UserAssignCall); std::optional MayBeAccessedBySelected = getAccessedLocalVariable(Selected); // If either doesn't access a local variable, we have to read from // there. if (not MayBeAccessedByUser.has_value() or not MayBeAccessedBySelected.has_value()) { ToReplaceWithAvailable[&U] = Selected; continue; } const Value *AccessedByUser = *MayBeAccessedByUser; const Value *AccessedBySelected = *MayBeAccessedBySelected; // If either is nullptr, there is at least one among I and J that // access many variables, so it's definitely not a single one and // cannot be optimized away. if (nullptr == AccessedByUser or nullptr == AccessedBySelected) { ToReplaceWithAvailable[&U] = Selected; continue; } // For all the other cases they are noAlias only if the accessed // local variable is different. if (AccessedByUser != AccessedBySelected) ToReplaceWithAvailable[&U] = Selected; else AssignToRemove.insert(UserAssignCall); } } } // 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. for (const auto &Assign : AssignToRemove) Picked.AssignToRemove.insert(Assign); // If we reach this point I is has not been picked for serialization yet, it // isn't a statement, and MemoryRead is available to all users of I either // directly or through some other local variable where the whole I is // available. // We have to recur in DFS fashion only on those uses for which we're using // MemoryRead directly (not through another local variable where I is // available). SmallSet UsersThatRequireMemoryReadSerialized; revng_log(Log, "recur on users that aren't available"); for (Use &TheUse : I->uses()) { if (auto It = Picked.ToReplaceWithAvailable.find(&TheUse); It != Picked.ToReplaceWithAvailable.end()) { revng_log(Log, "TheUse is available: " << dumpToString(TheUse.getUser())); continue; } User *TheUser = TheUse.getUser(); auto *UserInstruction = cast(TheUser); if (rc_recur shouldSerializeReadBeforeOrAtI(UserInstruction, MemoryRead)) UsersThatRequireMemoryReadSerialized.insert(UserInstruction); } size_t NumUsersRequiringSerialization = UsersThatRequireMemoryReadSerialized .size(); // If no users require MemoryRead to be serialized before them, there's // nothing to do, and I doesn't require MemoryRead to be serialized either. if (NumUsersRequiringSerialization == 0) { revng_log(Log, "No User requires MemoryRead to be serialized"); rc_return false; } if (I == MemoryRead) { revng_log(Log, "I == MemoryRead: Picked.ToSerialize.insert(I)"); revng_assert(not IType->isVoidTy() and not IType->isAggregateType() and not isCallToTagged(I, FunctionTags::IsRef)); Picked.ToSerialize.insert(I); rc_return false; } // If some users of I require MemoryRead to be serialized before them, // just serialize I. revng_log(Log, "Some of I's users require MemoryRead to be serialized"); revng_log(Log, "Try and serialize I"); if (IType->isVoidTy() or IType->isAggregateType() or isCallToTagged(I, FunctionTags::IsRef)) { revng_log(Log, "I can't be serialized, propagate up."); rc_return true; } else { revng_log(Log, "Picked.ToSerialize.insert(I)"); Picked.ToSerialize.insert(I); rc_return false; } revng_abort(); rc_return true; } void pickFrom(Instruction *I, Instruction *MemoryRead) { shouldSerializeReadBeforeOrAtI(I, MemoryRead); } private: Function &F; const ProgramPointsGraphWithInstructionMap &Graph; const ResultMap &MFPResultMap; PickedInstructions Picked; std::unordered_map ProgramOrdering; }; using TypeMap = std::map; class VariableBuilder { public: VariableBuilder(Function &TheF, const model::Binary &TheModel, TypeMap &&TMap) : Model(TheModel), TheTypeMap(std::move(TMap)), F(TheF), Builder(TheF.getContext()), LocalVarPool(FunctionTags::LocalVariable.getPool(*TheF.getParent())), AssignPool(FunctionTags::Assign.getPool(*TheF.getParent())), CopyPool(FunctionTags::Copy.getPool(*TheF.getParent())) {} public: bool run(const PickedInstructions &Picked) { bool Changed = false; for (const auto &[TheUse, TheAssign] : Picked.ToReplaceWithAvailable) { auto *UserInstruction = cast(TheUse->getUser()); Builder.SetInsertPoint(UserInstruction); auto *TheAddress = cast(TheAssign)->getArgOperand(1); // Create a Copy to dereference TheAssign auto *CopyFnType = getCopyType(TheAddress->getType()); auto *CopyFunction = CopyPool.get(TheAddress->getType(), CopyFnType, "Copy"); auto *Copy = Builder.CreateCall(CopyFunction, { TheAddress }); TheUse->set(Copy); } for (Instruction *I : Picked.AssignToRemove) { Changed = true; I->eraseFromParent(); } for (Instruction *I : Picked.ToSerialize) Changed |= serializeToLocalVariable(I); return Changed; } private: bool serializeToLocalVariable(Instruction *I); bool usesNeedToBeReplacedWithCopiesFromLocal(const Instruction *I) const; private: const model::Binary &Model; const TypeMap TheTypeMap; Function &F; IRBuilder<> Builder; OpaqueFunctionsPool LocalVarPool; OpaqueFunctionsPool AssignPool; OpaqueFunctionsPool CopyPool; }; bool VariableBuilder::usesNeedToBeReplacedWithCopiesFromLocal(const Instruction *I) const { 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. if (Layout.returnMethod() != abi::FunctionType::ReturnMethod::ModelAggregate) return true; unsigned NumUses = I->getNumUses(); // SPTAR return aggregates also need copies from local variables, // because they are emitted as scalar pointer variables in C. if (Layout.hasSPTAR()) { revng_assert(0 == NumUses); return true; } // Non-SPTAR return aggregates 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. for (const llvm::Use &U : I->uses()) { revng_assert(1 == U.getOperandNo() and (isCallToTagged(U.getUser(), FunctionTags::AddressOf) or isCallToTagged(U.getUser(), FunctionTags::ModelGEPRef) or isCallToTagged(U.getUser(), FunctionTags::Assign))); } return false; } bool VariableBuilder::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. Builder.SetInsertPoint(&F.getEntryBlock().front()); auto *IType = I->getType(); auto *LocalVarFunctionType = getLocalVarType(IType); auto *LocalVarFunction = LocalVarPool.get(IType, LocalVarFunctionType, "LocalVariable"); // Compute the model type returned from the call. const model::UpcastableType &VariableType = TheTypeMap.at(I); const llvm::DataLayout &DL = I->getModule()->getDataLayout(); auto ModelSize = VariableType->size().value(); auto IRSize = DL.getTypeStoreSize(IType); if (ModelSize < IRSize) { revng_assert(IType->isPointerTy()); using model::Architecture::getPointerSize; auto PtrSize = getPointerSize(Model.Architecture()); revng_assert(ModelSize == PtrSize); } else if (ModelSize > IRSize) { auto &Prototype = *getCallSitePrototype(Model, cast(I)); using namespace abi::FunctionType; abi::FunctionType::Layout Layout = Layout::make(Prototype); revng_assert(Layout.returnMethod() == ReturnMethod::ModelAggregate); if (Layout.hasSPTAR()) revng_assert(0 == I->getNumUses()); } // TODO: until we don't properly handle variable declarations with inline // initialization (might need MLIR), we cannot declare const local // variables, because their initialization (which is forcibly out-of-line) // would assign them and fail to compile. // For this reason if at this point we're trying to declare a constant // local variable, we're forced to throw away the constness information. Constant *TString = toLLVMString(model::getNonConst(*VariableType), *F.getParent()); // Inject call to LocalVariable CallInst *LocalVarCall = Builder.CreateCall(LocalVarFunction, { TString }); // 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. bool DoCopy = usesNeedToBeReplacedWithCopiesFromLocal(I); for (Use &U : llvm::make_early_inc_range(I->uses())) { revng_assert(isa(U.getUser())); Builder.SetInsertPoint(cast(U.getUser())); llvm::Value *ValueToUse = LocalVarCall; if (DoCopy) { // Create a Copy to dereference the LocalVariable auto *CopyFnType = getCopyType(LocalVarCall->getType()); auto *CopyFunction = CopyPool.get(LocalVarCall->getType(), CopyFnType, "Copy"); ValueToUse = Builder.CreateCall(CopyFunction, { LocalVarCall }); } U.set(ValueToUse); } // We have to assign the result of I to the local variable, right // after I itself. Builder.SetInsertPoint(I->getParent(), std::next(I->getIterator())); // Inject Assign() function auto *AssignFnType = getAssignFunctionType(IType, LocalVarCall->getType()); auto *AssignFunction = AssignPool.get(IType, AssignFnType, "Assign"); Builder.CreateCall(AssignFunction, { I, LocalVarCall }); return true; } bool SwitchToStatements::runOnFunction(Function &F) { revng_log(Log, "SwitchToStatements: " << F.getName()); ProgramPointsGraphWithInstructionMap Graph = makeProgramPointsWithInstructionsGraph(F); ResultMap Result = getMFP(&Graph.ProgramPointsGraph); auto &ModelWrapper = getAnalysis().get(); const TupleTree &Model = ModelWrapper.getReadOnlyModel(); auto ModelFunction = llvmToModelFunction(*Model, F); revng_assert(ModelFunction != nullptr); InstructionToSerializePicker InstructionPicker{ F, Graph, Result }; VariableBuilder VarBuilder{ F, *Model, initModelTypes(F, ModelFunction, *Model, /*PointerOnly*/ false) }; bool Changed = VarBuilder.run(InstructionPicker.pick()); return Changed; } char SwitchToStatements::ID = 0; using Register = RegisterPass; static Register X("switch-to-statements", "SwitchToStatements", false, false);