#pragma once // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include #include #include "llvm/ADT/DepthFirstIterator.h" #include "llvm/ADT/GraphTraits.h" #include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/SCCIterator.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/IR/Dominators.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Instructions.h" #include "llvm/Support/Casting.h" #include "llvm/Support/GenericDomTreeConstruction.h" #include "llvm/Support/raw_os_ostream.h" #include "revng/ADT/ReversePostOrderTraversal.h" #include "revng/MFP/MFP.h" #include "revng/RestructureCFG/ASTTree.h" #include "revng/RestructureCFG/BasicBlockNodeBB.h" #include "revng/RestructureCFG/MetaRegionBB.h" #include "revng/RestructureCFG/RegionCFGTree.h" #include "revng/RestructureCFG/Utils.h" #include "revng/Support/GraphAlgorithms.h" #include "revng/Support/IRHelpers.h" template bool intersects(IterT I1, IterT E1, IterT I2, IterT E2) { while ((I1 != E1) and (I2 != E2)) { if (*I1 < *I2) ++I1; else if (*I2 < *I1) ++I2; else return true; } return false; } template bool isDisjoint(IterT I1, IterT E1, IterT I2, IterT E2) { return not intersects(I1, E1, I2, E2); } template bool intersects(const RangeT &R1, const RangeT &R2) { return intersects(R1.begin(), R1.end(), R2.begin(), R2.end()); } template bool isDisjoint(const RangeT &R1, const RangeT &R2) { return not intersects(R1, R2); } unsigned const SmallSetSize = 16; // llvm::SmallPtrSet is a handy way to store set of BasicBlockNode pointers. template using SmallPtrSet = llvm::SmallPtrSet *, SmallSetSize>; template inline void RegionCFG::setFunctionName(std::string Name) { FunctionName = Name; } template inline void RegionCFG::setRegionName(std::string Name) { RegionName = Name; } template inline std::string RegionCFG::getFunctionName() const { return FunctionName; } template inline std::string RegionCFG::getRegionName() const { return RegionName; } template inline BasicBlockNode * RegionCFG::addNode(NodeT Node, llvm::StringRef Name) { using BBNodeT = BasicBlockNodeT; BlockNodes.emplace_back(std::make_unique(this, Node, Name)); BasicBlockNodeT *Result = BlockNodes.back().get(); revng_log(CombLogger, "Building " << Name << " at address: " << Result << "\n"); return Result; } template inline BasicBlockNode * RegionCFG::cloneNode(BasicBlockNodeT &OriginalNode) { using BBNodeT = BasicBlockNodeT; BlockNodes.emplace_back(std::make_unique(OriginalNode, this)); BasicBlockNodeT *New = BlockNodes.back().get(); New->setName(OriginalNode.getName().str() + " cloned"); New->setWeaved(OriginalNode.isWeaved()); return New; } template inline void RegionCFG::removeNode(BasicBlockNodeT *Node) { revng_log(CombLogger, "Removing node named: " << Node->getNameStr() << "\n"); for (BasicBlockNodeT *Predecessor : Node->predecessors()) Predecessor->removeSuccessor(Node); for (BasicBlockNodeT *Successor : Node->successors()) Successor->removePredecessor(Node); for (auto It = BlockNodes.begin(); It != BlockNodes.end(); It++) { if ((*It).get() == Node) { DeadNodesQuarantine.push_back(std::move(*It)); BlockNodes.erase(It); break; } } } template using BBNodeT = typename RegionCFG::BasicBlockNodeT; template inline void copyNeighbors(BBNodeT *Dst, BBNodeT *Src) { for (const auto &P : Src->labeled_successors()) Dst->addLabeledSuccessor(P); for (const auto &P : Src->labeled_predecessors()) Dst->addLabeledPredecessor(P); } template inline void RegionCFG::insertBulkNodes(BasicBlockNodeTSet &Nodes, BasicBlockNodeT *Head, BBNodeMap &SubMap, std::set &Out, llvm::SmallVector &ContinueBackedges) { revng_assert(BlockNodes.empty()); for (BasicBlockNodeT *Node : Nodes) { BlockNodes.emplace_back(std::make_unique(*Node, this)); BasicBlockNodeT *New = BlockNodes.back().get(); SubMap[Node] = New; // The copy constructor used above does not bring along the successors and // the predecessors, neither adjusts the parent. // The following lines are a hack to fix this problem, but they momentarily // build a broken data structure where the predecessors and the successors // of the New BasicBlockNodes in *this still refer to the BasicBlockNodes in // the Parent CFGRegion of Nodes. This will be fixed later by updatePointers copyNeighbors(New, Node); } // We now create the break nodes, and put in the `SubMap` the correspondence // between each target of the outgoing edges, and the newly created break // nodes. The adjustment of the break target must be handled now and not // postponed in a later stage, in order to avoid losing the ordering of // successors (e.g., then and else, if then goes to a break). // In addition, since multiple break can go to the same successors, we keep a // mapping of successor -> corresponding break, so that we can reuse it. BBNodeMap BreakMap; for (EdgeDescriptor Edge : Out) { // Check if we already have a break for each outgoing edge, or create it. BasicBlockNodeT *Break = nullptr; auto It = BreakMap.find(Edge.second); if (It != BreakMap.end()) { Break = It->second; } else { Break = addBreak(); BreakMap[Edge.second] = Break; } // Extract from the old predecessor edge the corresponding labels. auto OldPredEdgeWithLabels = Edge.second->getPredecessorEdge(Edge.first); auto &OldEdgeInfo = OldPredEdgeWithLabels.second; // We add the old predecessor, so that when `updatePointers` is called it // will adjust the predecessor to the correspondent one found in the // `SubMap`. Break->addLabeledPredecessor(std::make_pair(Edge.first, OldEdgeInfo)); // We leave to the `updatePointers` helper the task of adding the break as // new target for the exiting node, by inserting specific information in the // `SubMap`. SubMap[Edge.second] = Break; } revng_assert(Head != nullptr); EntryNode = SubMap[Head]; revng_assert(EntryNode != nullptr); // Fix the hack above for (BBNodeTUniquePtr &Node : BlockNodes) Node->updatePointers(SubMap); // Connect all the `ContinueBackedges` to `continue` nodes for (EdgeDescriptor &Backedge : ContinueBackedges) { // Confirm that the retreating edge points to the `Head` (the previous entry // node) revng_assert(Backedge.second == Head); // Create a new continue node for each retreating edge. BasicBlockNodeT *Continue = addContinue(); BasicBlockNodeT *RetreatingSource = SubMap[Backedge.first]; revng_assert(RetreatingSource != nullptr); moveEdgeTarget(EdgeDescriptor(RetreatingSource, EntryNode), Continue); } // After the processing, confirm that the `EntryNode` has no more predecessor revng_assert(EntryNode->predecessor_size() == 0); } template template inline void RegionCFG::streamNode(StreamT &S, const BasicBlockNodeT *BB) const { unsigned NodeID = BB->getID(); S << "\"" << NodeID << "\""; S << " [" << "label=\"ID: " << NodeID << " Name: " << BB->getNameStr(); if (BB->isCollapsed()) S << " Idx: " << BB->getCollapsedRegionName(); S << "\""; if (BB == EntryNode) S << ",fillcolor=green,style=filled"; S << "];\n"; } /// Dump a GraphViz file on stdout representing this function template template inline void RegionCFG::dumpDot(StreamT &S) const { S << "digraph CFGFunction {\n"; for (const std::unique_ptr> &BB : BlockNodes) { streamNode(S, BB.get()); unsigned Counter = 0; for (const auto &[Successor, EdgeInfo] : BB->labeled_successors()) { unsigned PredID = BB->getID(); unsigned SuccID = Successor->getID(); S << "\"" << PredID << "\"" << " -> \"" << SuccID << "\""; if (EdgeInfo.Inlined) S << " [color=purple, label=" << Counter << "];\n"; else S << " [color=green, label=" << Counter << "];\n"; Counter++; } } S << "}\n"; } template inline void RegionCFG::dumpCFGOnFile(const std::string &FileName) const { std::error_code EC; llvm::raw_fd_ostream DotFile(FileName, EC); revng_check(not EC, "Could not open file for printing RegionCFG dot"); dumpDot(DotFile); } template inline void RegionCFG::dumpCFGOnFile(const std::string &FuncName, const std::string &FolderName, const std::string &FileName) const { const std::string GraphDir = "debug-graphs"; std::error_code EC = llvm::sys::fs::create_directory(GraphDir); revng_check(not EC, "Could not create directory to print RegionCFG dot"); EC = llvm::sys::fs::create_directory(GraphDir + "/" + FuncName); revng_check(not EC, "Could not create directory to print RegionCFG dot"); const std::string PathName = GraphDir + "/" + FuncName + "/" + FolderName; EC = llvm::sys::fs::create_directory(PathName); revng_check(not EC, "Could not create directory to print RegionCFG dot"); dumpCFGOnFile(PathName + "/" + FileName); } template inline bool RegionCFG::purgeIfTrivialDummy(BBNodeT *Dummy) { RegionCFG &Graph = *this; revng_assert(not Dummy->isEmpty() or Dummy->predecessor_size() != 0); if ((Dummy->isEmpty()) and (Dummy->predecessor_size() == 1) and (Dummy->successor_size() == 1)) { revng_log(CombLogger, "Purging dummy node " << Dummy->getNameStr()); BasicBlockNode *Predecessor = *Dummy->predecessors().begin(); BasicBlockNode *Successor = *Dummy->successors().begin(); // Connect directly predecessor and successor, and remove the dummy node // under analysis moveEdgeTarget({ Predecessor, Dummy }, Successor); Graph.removeNode(Dummy); return true; } return false; } template inline bool RegionCFG::purgeTrivialDummies() { RegionCFG &Graph = *this; bool RemovedNow = true; bool Removed = false; while (RemovedNow) { RemovedNow = false; for (auto *Node : Graph) { RemovedNow = purgeIfTrivialDummy(Node); if (RemovedNow) { Removed = true; break; } } } return Removed; } template inline void RegionCFG::purgeVirtualSink(BasicBlockNode *Sink) { RegionCFG &Graph = *this; BasicBlockNodeTVect WorkList; BasicBlockNodeTVect PurgeList; WorkList.push_back(Sink); while (!WorkList.empty()) { BasicBlockNode *CurrentNode = WorkList.back(); WorkList.pop_back(); if (CurrentNode->isEmpty()) { PurgeList.push_back(CurrentNode); for (BasicBlockNode *Predecessor : CurrentNode->predecessors()) { WorkList.push_back(Predecessor); } } } for (BasicBlockNode *Purge : PurgeList) { Graph.removeNode(Purge); } } inline bool isGreater(unsigned Op1, unsigned Op2) { unsigned MultiplicativeFactor = 1; if (Op1 > (MultiplicativeFactor * Op2)) { return true; } else { return false; } } template inline BasicBlockNode * RegionCFG::cloneUntilExit(BasicBlockNode *Node, BasicBlockNode *Sink) { // Clone the postdominator node. BBNodeMap CloneMap; BasicBlockNode *Clone = cloneNode(*Node); // Insert the postdominator clone in the map. CloneMap[Node] = Clone; BasicBlockNodeTVect WorkList; WorkList.push_back(Node); // Set of nodes which have been already processed. BasicBlockNodeTSet AlreadyProcessed; while (!WorkList.empty()) { BasicBlockNode *CurrentNode = WorkList.back(); WorkList.pop_back(); // Ensure that we are not processing the sink node. revng_assert(CurrentNode != Sink); auto &&[_, Inserted] = AlreadyProcessed.insert(CurrentNode); if (!Inserted) continue; // Get the clone of the `CurrentNode`. BasicBlockNode *CurrentClone = CloneMap.at(CurrentNode); for (const auto &[Succ, Labels] : CurrentNode->labeled_successors()) { // If the successor is not the sink, create and edge that directly // connects it. if (Succ != Sink) { BasicBlockNode *SuccessorClone = nullptr; // The clone of the successor node already exists. auto CloneIt = CloneMap.find(Succ); if (CloneIt != CloneMap.end()) { SuccessorClone = CloneIt->second; } else { // The clone of the successor does not exist, create it in place. SuccessorClone = cloneNode(*Succ); CloneMap[Succ] = SuccessorClone; } // Create the edge to the clone of the successor. revng_assert(SuccessorClone != nullptr); addEdge(EdgeDescriptor(CurrentClone, SuccessorClone), Labels); // Add the successor to the worklist. WorkList.push_back(Succ); } } } return Clone; } template inline void RegionCFG::untangle() { // TODO: Here we handle only conditional nodes with two successors. We should // consider extending the untangle procedure also to conditional nodes // with more than two successors (switch nodes). revng_assert(isDAG()); RegionCFG &Graph = *this; // Add a new virtual sink node to computer the postdominator. BasicBlockNode *Sink = Graph.addArtificialNode("Sink"); for (auto *Node : Graph) if (Node != Sink and Node->successor_size() == 0) addPlainEdge(EdgeDescriptor(Node, Sink)); if (CombLogger.isEnabled()) { Graph.dumpCFGOnFile(FunctionName, "untangle", "region-" + RegionName + "-before-untangle"); } // Map which contains the precomputed weight for each node in the graph. In // case of a code node the weight will be equal to the number of instruction // in the original basic block; in case of a collapsed node the weight will be // the sum of the weights of all the nodes contained in the collapsed graph. std::map *, size_t> WeightMap; for (BasicBlockNode *Node : Graph.nodes()) { WeightMap[Node] = Node->getWeight(); } // Collect all the conditional nodes in the graph into a vector sorted in // Reverse Post-Order. BasicBlockNodeTVect ConditionalNodes; { BasicBlockNodeTSet ConditionalNodesSet; for (auto *Node : Graph) if (Node->successor_size() == 2) ConditionalNodesSet.insert(Node); llvm::ReversePostOrderTraversal *> RPOT(EntryNode); for (BasicBlockNode *RPOTBB : RPOT) { if (ConditionalNodesSet.contains(RPOTBB)) { ConditionalNodes.push_back(RPOTBB); } } } while (not ConditionalNodes.empty()) { BasicBlockNode *Conditional = ConditionalNodes.back(); ConditionalNodes.pop_back(); // Update the information of the dominator and postdominator trees. DT.recalculate(Graph); IFPDT.recalculate(Graph); // Update the postdominator BasicBlockNodeT *PostDominator = IFPDT[Conditional]->getIDom()->getBlock(); // Ensure that we have both the successors. revng_assert(Conditional->successor_size() == 2); // Get the first node of the then and else branches respectively. // TODO: Check that this is the right way to do this. At this point we // cannot assume that we have the `getThen()` and `getFalse()` // methods. BasicBlockNode *ThenChild = Conditional->getSuccessorI(0); BasicBlockNode *ElseChild = Conditional->getSuccessorI(1); // Collect all the nodes laying between the branches llvm::SmallSetVector *, 4> ThenNodes; llvm::SmallSetVector *, 4> ElseNodes; // If the `PostDominator` is present, we use the `nodesBetween` primitive to // stop at the `PostDominator`, otherwise we collect all the reachable nodes if (PostDominator != nullptr) { ThenNodes = nodesBetween(ThenChild, PostDominator); ElseNodes = nodesBetween(ElseChild, PostDominator); } else { ThenNodes = findReachableNodes(ThenChild); ElseNodes = findReachableNodes(ElseChild); } // Remove the postdominator from both the sets. ThenNodes.remove(PostDominator); ElseNodes.remove(PostDominator); const auto EdgeDominates = [DT = &DT](const EdgeDescriptor &E, BasicBlockNodeT *N) { const auto &[Src, Dst] = E; if (not DT->dominates(Dst, N)) return false; if (Dst->predecessor_size() < 2) return true; bool DuplicateEdge = false; for (BasicBlockNodeT *Pred : Dst->predecessors()) { if (Pred == Src) { if (DuplicateEdge) return false; DuplicateEdge = true; continue; } if (not DT->dominates(Dst, Pred)) return false; } return true; }; // New implementation of the dominance criterion which uses the then and // else edges to compute the dominance. if (EdgeDominates({ Conditional, ElseChild }, ElseChild)) { const auto DominatedByElse = [DT = &DT, ElseChild](auto *Node) { return DT->dominates(ElseChild, Node); }; ElseNodes.remove_if(DominatedByElse); } if (EdgeDominates({ Conditional, ThenChild }, ThenChild)) { const auto DominatedByThen = [DT = &DT, ThenChild](auto *Node) { return DT->dominates(ThenChild, Node); }; ThenNodes.remove_if(DominatedByThen); } // Compute the weight of the `then` and `else` branches. unsigned ThenWeight = 0; unsigned ElseWeight = 0; for (BasicBlockNode *Node : ThenNodes) { ThenWeight += WeightMap[Node]; } for (BasicBlockNode *Node : ElseNodes) { ElseWeight += WeightMap[Node]; } // The weight of the nodes placed after the immediate postdominator is the // sum of all the weights of the nodes which are reachable starting from the // immediate post dominator and the sink node (to which all the exits have // been connected). // If the post dominator is `nullptr` (meaning that it is the `VirtualRoot` // node on the filtered post dominator tree), we can skip the computation of // this weight. unsigned PostDominatorWeight = 0; if (PostDominator != nullptr) { llvm::SmallSetVector *, 4> PostDominatorToExit = nodesBetween(PostDominator, Sink); for (BasicBlockNode *Node : PostDominatorToExit) { PostDominatorWeight += WeightMap[Node]; } } // Criterion which decides if we can apply the untangle optimization to the // conditional under analysis. // We define 3 weights: // - 1) weight(then) + weight(else) // - 2) weight(then) + weight(postdom) // - 3) weight(else) + weight(postdom) // // We need to operate the split if: // 2 >> 3 // 1 >> 3 // and specifically we need to split the `else` branch. // // We need to operate the split if: // 3 >> 2 // 1 >> 2 // and specifically we need to split the `then` branch. // // We can also define in a dynamic way the >> operator, so we can change the // threshold that triggers the split. unsigned CombingCost = ThenWeight + ElseWeight; unsigned UntangleThenCost = ThenWeight + PostDominatorWeight; unsigned UntangleElseCost = ElseWeight + PostDominatorWeight; unsigned UntanglingCost = std::min(UntangleThenCost, UntangleElseCost); if (isGreater(CombingCost, UntanglingCost)) { revng_log(CombLogger, FunctionName << ":"); revng_log(CombLogger, RegionName << ":"); revng_log(CombLogger, "Found untangle candidate " << Conditional->getNameStr()); revng_log(CombLogger, "CombingCost:" << CombingCost); revng_log(CombLogger, "UntangleThenCost:" << UntangleThenCost); revng_log(CombLogger, "UntangleElseCost:" << UntangleElseCost); // Register a tentative untangle in the dedicated counter. UntangleTentativeCounter++; // Register an actual untangle in the dedicated counter. UntanglePerformedCounter++; revng_log(CombLogger, "Actually splitting node"); auto *ToUntangle = (UntangleThenCost > UntangleElseCost) ? ElseChild : ThenChild; // Perform the split from the first node of the then/else branches. // We fully inline all the nodes belonging to the branch we are untangling // till the exit node. BasicBlockNode *UntangledChild = cloneUntilExit(ToUntangle, Sink); // Move the edge coming out of the conditional node to the new clone of // the node. moveEdgeTarget(EdgeDescriptor(Conditional, ToUntangle), UntangledChild); // We mark the edge going into the `UntangleChild` as an inlined edge. // In this way, in all the next phases, these edges will be ignored by the // dominator and postdominator trees. markEdgeInlined(EdgeDescriptor(Conditional, UntangledChild)); // Remove nodes that have no predecessors (nodes that are the result of // node cloning and that remains dandling around). // While doing this, update InlineEdges. bool Removed = true; while (Removed) { Removed = false; BasicBlockNode *Entry = &getEntryNode(); for (auto It = begin(); It != end(); ++It) { if ((Entry != *It and (*It)->predecessor_size() == 0)) { removeNode(*It); Removed = true; break; } } } } } if (CombLogger.isEnabled()) { Graph.dumpCFGOnFile(FunctionName, "untangle", "region-" + RegionName + "-after-untangle"); } // Remove the sink node. purgeVirtualSink(Sink); if (CombLogger.isEnabled()) { Graph.dumpCFGOnFile(FunctionName, "untangle", "region-" + RegionName + "-after-untangle-after-sink-removal"); } } template struct ReachableExitsAnalysis : public SetUnionLattice *>> { using Label = BasicBlockNode *; using GraphType = RegionCFG *; using LatticeElement = typename SetUnionLattice< std::set *>>::LatticeElement; static LatticeElement applyTransferFunction(const Label &L, const LatticeElement E) { const auto IsInlined = [](const auto &NodeLabelPair) { return NodeLabelPair.second.Inlined; }; if (bool IsExit = llvm::all_of(L->labeled_successors(), IsInlined); IsExit) return { L }; return E; } }; template inline void RegionCFG::inflate() { // Call the untangle preprocessing. untangle(); revng_assert(isDAG()); // Apply the comb to a RegionCFG object. RegionCFG &Graph = *this; BasicBlockNode *Entry = &Graph.getEntryNode(); if (CombLogger.isEnabled()) { revng_log(CombLogger, "Entry node is: " << Entry->getNameStr()); Graph.dumpCFGOnFile(FunctionName, "inflate", "region-" + RegionName + "-before-inflate"); } // Collect the sets of reachable exits from each node that is a successor of a // node that induces duplication. std::vector *> Exits; for (auto *Exit : Graph) if (llvm::all_of(Exit->labeled_successors(), [](const auto &Pair) { return Pair.second.Inlined; })) Exits.push_back(Exit); revng_log(CombLogger, "Num exits: " << Exits.size()); revng_log(CombLogger, "Region Size: " << Graph.size()); using REA = ReachableExitsAnalysis; using Inverse = llvm::Inverse; auto ReachableExits = MFP::getMaximalFixedPoint< REA, llvm::GraphTraits, llvm::Inverse *>>({}, &Graph, {}, {}, {}, Exits); // Refresh information of dominator and postdominator trees. DT.recalculate(Graph); IFPDT.recalculate(Graph); // Map to hold, for each conditional node that initiates combing, the node // that will be used to detect the point where combing needs to stop // duplicating node. This is the immediate post dominator for most nodes, but // we have a special case for the case nodes of switches. BBNodeMap ConditionalToCombEnd; // Collect all the conditional nodes in the graph. // This is the working list of conditional nodes on which we will operate and // will contain only the filtered conditionals. BasicBlockNodeTSet ConditionalNodesSet; std::vector *> Switches; for (BBNodeT *Node : Graph) { switch (Node->successor_size()) { case 0: case 1: // We don't need to add it to the conditional nodes vector. break; case 2: { BasicBlockNodeTSet ThenExits = ReachableExits.at(Node->getSuccessorI(0)) .OutValue; BasicBlockNodeTSet ElseExits = ReachableExits.at(Node->getSuccessorI(1)) .OutValue; // Add the conditional node to the set of nodes processed by the inflate. ConditionalNodesSet.insert(Node); BasicBlockNode *PostDom = IFPDT[Node]->getIDom()->getBlock(); bool New = ConditionalToCombEnd.insert({ Node, PostDom }).second; revng_assert(New); // If the exit nodes reachable from the Then and from the Else are not // disjoint, then the conditional node is not eligible for having its // successor nodes marked as inlined. if (not isDisjoint(ThenExits, ElseExits)) break; // Check that we do not dominate at maximum on of the two sets of // reachable exits. bool ThenIsDominated = true; bool ElseIsDominated = true; for (BasicBlockNode *Exit : ThenExits) { if (not DT.dominates(Node, Exit)) { ThenIsDominated = false; break; } } for (BasicBlockNode *Exit : ElseExits) { if (not DT.dominates(Node, Exit)) { ElseIsDominated = false; break; } } // If there is one set of exits that Node entirely dominates, we can // blacklist it because it will never cause duplication. // The reason is that the set of exits that we dominate can be completely // inlined and absorbed either into the then or into the else. if (ThenIsDominated or ElseIsDominated) { revng_log(CombLogger, "Blacklisted conditional: " << Node->getNameStr()); // Mark then or else edges as inlined during the conditional // blacklisting. In case both the `then` and `else` branches are // completely dominated, we mark both as inlineable. BasicBlockNodeT *Then = Node->getSuccessorI(0); BasicBlockNodeT *Else = Node->getSuccessorI(1); // TODO: Verify that, for nodes where the inling is applied to both the // branches, the immediate post-dominator on the filtered postdom // is the conditional node itself. Verify also that, if this is // the situation, the inflating procedure does not go in a loop // (it should stop immediately if conditional == postdom). if (ThenIsDominated and ElseIsDominated) { markEdgeInlined(EdgeDescriptor(Node, Then)); markEdgeInlined(EdgeDescriptor(Node, Else)); } else if (ThenIsDominated) { markEdgeInlined(EdgeDescriptor(Node, Then)); } else if (ElseIsDominated) { markEdgeInlined(EdgeDescriptor(Node, Else)); } else { revng_abort(); } } } break; default: { Switches.push_back(Node); } break; } } for (auto *Switch : Switches) { llvm::SmallPtrSet *, 8> CaseNodes; for (auto *SwitchCase : Switch->successors()) CaseNodes.insert(SwitchCase); for (auto *Case : CaseNodes) { auto *DummyCase = addArtificialNode("dummy case"); moveEdgeTarget(EdgeDescriptor(Switch, Case), DummyCase); addPlainEdge(EdgeDescriptor(DummyCase, Case)); ConditionalNodesSet.insert(DummyCase); BasicBlockNode *PostDom = IFPDT[Switch]->getIDom()->getBlock(); // Combing of switch cases continues until the post dominator of the // switch, not until the post dominator of the case. bool New = ConditionalToCombEnd.insert({ DummyCase, PostDom }).second; revng_assert(New); } } if (CombLogger.isEnabled()) { revng_log(CombLogger, "Conditional nodes present in the graph are:"); for (BasicBlockNode *Node : ConditionalNodesSet) revng_log(CombLogger, Node->getNameStr()); } // Equivalence-class like set to keep track of all the cloned nodes created // starting from an original node. std::map *, SmallPtrSet> NodesEquivalenceClass; // Map to keep track of the cloning relationship. BBNodeMap CloneToOriginalMap; // Initialize a list containing the reverse post order of the nodes of the // graph. std::list *> RevPostOrderList; // Vector of conditional nodes, to be filled in reverse post order. BasicBlockNodeTVect ConditionalNodes; llvm::ReversePostOrderTraversal *> RPOT(Entry); for (BasicBlockNode *RPOTBB : RPOT) { RevPostOrderList.push_back(RPOTBB); NodesEquivalenceClass[RPOTBB].insert(RPOTBB); CloneToOriginalMap[RPOTBB] = RPOTBB; if (ConditionalNodesSet.contains(RPOTBB)) ConditionalNodes.push_back(RPOTBB); } NodesEquivalenceClass[nullptr] = {}; // CFGDumper used to incrementally print the combing evolution CFGDumper Dumper(Graph, FunctionName, RegionName, "inflate"); // Iterate on ConditionalNodes from the back. Given that they are inserted // into ConditionalNodes in RPOT, this iteration is in post-order. while (not ConditionalNodes.empty()) { // Process each conditional node after ordering it. BasicBlockNode *Conditional = ConditionalNodes.back(); ConditionalNodes.pop_back(); // Retrieve a reference to the set of postdominators. auto CombEndIt = ConditionalToCombEnd.find(Conditional); revng_assert(CombEndIt != ConditionalToCombEnd.end()); auto CombEndSetIt = NodesEquivalenceClass.find(CombEndIt->second); revng_assert(CombEndSetIt != NodesEquivalenceClass.end()); if (CombLogger.isEnabled()) { revng_log(CombLogger, "Analyzing conditional node " << Conditional->getNameStr()); Dumper.log("-conditional-" + Conditional->getNameStr() + "-initial-state"); } // List to keep track of the nodes that we still need to analyze. SmallPtrSet WorkList; // Enqueue in the worklist the successors of the contional node. for (auto &[Successor, EdgeLabel] : Conditional->labeled_successors()) if (not EdgeLabel.Inlined) WorkList.insert(Successor); // Keep a set of the visited nodes for the current conditional node. SmallPtrSet Visited = { Conditional }; // Get an iterator from the reverse post order list in the position of the // conditional node. auto ListIt = std::find(RevPostOrderList.begin(), RevPostOrderList.end(), Conditional); revng_assert(ListIt != RevPostOrderList.end()); int Iteration = 0; while (++ListIt != RevPostOrderList.end() and not WorkList.empty()) { if (not WorkList.contains(*ListIt)) continue; // Go to the next node in reverse postorder. // Otherwise this node is in the worklist, and we have to analyze it. BasicBlockNode *Candidate = *ListIt; revng_assert(Candidate != nullptr); revng_log(CombLogger, "Analyzing candidate " << Candidate->getNameStr()); bool AllPredAreVisited = std::all_of(Candidate->predecessors().begin(), Candidate->predecessors().end(), [&Visited](auto *Pred) { return Visited.contains(Pred); }); WorkList.erase(Candidate); Visited.insert(Candidate); // Comb end flag, which is useful to understand if the dummies we will // insert will need to substitute the current postdominator. bool IsCombEnd = CombEndSetIt->second.contains(Candidate); if (not IsCombEnd) { for (auto &[Successor, EdgeLabel] : Candidate->labeled_successors()) { WorkList.insert(Successor); } } else { revng_log(CombLogger, Candidate->getNameStr() << " is Post-Dominator of " << Conditional->getNameStr()); } if (AllPredAreVisited) continue; // Go to the next node in reverse postorder. if (IsCombEnd) { revng_assert(Candidate->predecessor_size() > 1); llvm::SmallVector *, 8> NewDummyPredecessors; revng_log(CombLogger, "Current predecessors are:"); for (BasicBlockNode *Predecessor : Candidate->predecessors()) { revng_log(CombLogger, Predecessor->getNameStr()); if (Visited.contains(Predecessor)) NewDummyPredecessors.push_back(Predecessor); } // We don't insert the dummy, because it would be a dummy with a // single predecessor and a single successor, which is pointless. if (NewDummyPredecessors.size() < 2) continue; revng_log(CombLogger, "Inserting a dummy node for " << Candidate->getNameStr()); // Insert a dummy node. Notice, this is guaranteed not to be trivial // because it will have more than one predecessor. BasicBlockNode *Dummy = Graph.addArtificialNode(); for (BasicBlockNode *Predecessor : NewDummyPredecessors) { revng_log(CombLogger, "Moving edge from predecessor " << Predecessor->getNameStr() << " to dummy"); moveEdgeTarget(EdgeDescriptor(Predecessor, Candidate), Dummy); } addPlainEdge(EdgeDescriptor(Dummy, Candidate)); // Remove from the visited set the node which triggered the creation // of the dummy nodes, because we're not really analyzing it now, // since we're just inserting the dummy. For the same reason we // re-insert it in the WorkList, otherwise it will be skipped at the // next iteration. Visited.erase(Candidate); // The new dummy node does not lead back to any original node, for // this reason we need to insert a new entry in the // `CloneToOriginalMap`. CloneToOriginalMap[Dummy] = Dummy; revng_log(CombLogger, "Update conditional post-dominator. Old: " << CombEndIt->second->getNameStr() << " New: " << Dummy->getNameStr()); // The dummy is now the node that ends the combing for Conditional. CombEndIt->second = Dummy; CombEndSetIt = NodesEquivalenceClass.insert({ Dummy, { Dummy } }).first; // Mark the dummy to explore. WorkList.insert(Dummy); // Insert the dummy nodes in the reverse post order list. The // insertion order is particularly relevant, because we have added a // dummy that now post-dominates the region starting from Conditional, // while Candidate (which is the post-dominator of Conditional here), // is a successor of Dummy. Hence Dummy must come first in reverse // post order, otherwise future RPOT visits based on RevPostOrderList // might be disrupted. auto PrevListIt = std::prev(ListIt); RevPostOrderList.insert(ListIt, Dummy); ListIt = PrevListIt; } else { // Duplicate node. DuplicationCounter++; revng_log(CombLogger, "Duplicating node " << Candidate->getNameStr()); BasicBlockNode *Duplicated = Graph.cloneNode(*Candidate); revng_assert(Duplicated != nullptr); // Initialize the successors of the Duplicated node with the same // successors of Candidate node for (const auto &[Succ, Label] : Candidate->labeled_successors()) addEdge(EdgeDescriptor(Duplicated, Succ), Label); bool Same = Candidate->successor_size() == Duplicated->successor_size(); revng_assert(Same); // Move Candidate's predecessors that have not been visited yet, so that // they become predecessors of Duplicated BasicBlockNodeTVect NotVisitedPredecessors; for (BasicBlockNode *Predecessor : Candidate->predecessors()) if (not Visited.contains(Predecessor)) NotVisitedPredecessors.push_back(Predecessor); for (BasicBlockNode *Predecessor : NotVisitedPredecessors) { moveEdgeTarget(EdgeDescriptor(Predecessor, Candidate), Duplicated); revng_log(CombLogger, "Moving edge from predecessor " << Predecessor->getNameStr() << " to " << Duplicated->getNameStr()); } if (CombLogger.isEnabled()) { Dumper.log("-conditional-" + Conditional->getNameStr() + "-before-purge-dummies-iteration-" + std::to_string(Iteration)); } BasicBlockNode *OriginalNode = CloneToOriginalMap.at(Candidate); bool AreDummies = Candidate->isEmpty(); revng_assert(AreDummies == Duplicated->isEmpty()); if (AreDummies) { revng_log(CombLogger, "Duplicated is dummy"); unsigned CandidateSuccSize = Candidate->successor_size(); unsigned DuplicatedSuccSize = Duplicated->successor_size(); revng_assert(CandidateSuccSize < 2 and DuplicatedSuccSize < 2); revng_assert(CandidateSuccSize == DuplicatedSuccSize); bool CInl = Candidate->labeled_successors().begin()->second.Inlined; bool DInl = Duplicated->labeled_successors().begin()->second.Inlined; revng_assert(CandidateSuccSize == 0 or CInl == false); revng_assert(DuplicatedSuccSize == 0 or DInl == false); // Notice: after this call Duplicated is invalid if the call returns // true, meaning that dereferencing it is bad. You can still use it as // a key or value into maps though. if (not purgeIfTrivialDummy(Duplicated)) { // Add the cloned node in the equivalence class of the original // node. CloneToOriginalMap[Duplicated] = OriginalNode; NodesEquivalenceClass.at(OriginalNode).insert(Duplicated); // If it wasn't purged, insert the cloned node in the reverse post // order list. Here the order is not strictly relevant, because // there is no strict relationship between Candidate and Duplicated. RevPostOrderList.insert(ListIt, Duplicated); } else { revng_log(CombLogger, "Duplicated is trivial"); } // The duplication process divides the edges incoming to Candidate, // and it moves some of them to Duplicated. If Candidate is a dummy // node, this process may make it trivial. In that case we want to // remove it. // Notice: after this call Candidate is invalid if the call returns // true, meaning that dereferencing it is bad. You can still use it as // a key or value into maps though. if (purgeIfTrivialDummy(Candidate)) { revng_log(CombLogger, "Candidate is now trivial"); CloneToOriginalMap.erase(Candidate); NodesEquivalenceClass.at(OriginalNode).erase(Candidate); Visited.erase(Candidate); // Erase Candidate from the post order list, but update ListIt so // that after the removal it points to the element that was // previously before Candidate. In this way, at the next iteration // of the loop on RevPostOrderList we go on with the element that // was right after Candidate before its removal. auto PrevListIt = std::prev(ListIt); RevPostOrderList.erase(ListIt); ListIt = PrevListIt; } } else { revng_log(CombLogger, "Duplicated is not dummy"); // Add the cloned node in the equivalence class of the original node. CloneToOriginalMap[Duplicated] = OriginalNode; NodesEquivalenceClass.at(OriginalNode).insert(Duplicated); // Insert the cloned node in the reverse post order list, right before // the Candidate. This is not important right now, because we don't // add it to the WorkList. It will become important if whenever // Duplicated is reached with a traversal based on RevPostOrderList // starting from a different Conditional. // In this sense, it's not really important to insert Duplicated // before or after Candidate, since they have no strict relationship // in the reverse post order. RevPostOrderList.insert(ListIt, Duplicated); } } if (CombLogger.isEnabled()) { Dumper.log("-conditional-" + Conditional->getNameStr() + "-after-processing-iteration-" + std::to_string(Iteration)); } Iteration++; } revng_log(CombLogger, "Finished looking at conditional: " << Conditional->getNameStr()); Dumper.log("-conditional-" + Conditional->getNameStr() + "-final-state"); } if (CombLogger.isEnabled()) { Graph.dumpCFGOnFile(FunctionName, "inflate", "region-" + RegionName + "-after-inflate-before-cleanup"); } // Purge extra dummy nodes introduced. purgeTrivialDummies(); if (CombLogger.isEnabled()) { Graph.dumpCFGOnFile(FunctionName, "inflate", "region-" + RegionName + "-after-inflate"); } revng_log(CombLogger, "Region Final Size: " << Graph.size()); } template inline void RegionCFG::removeNotReachables() { // Remove nodes that have no predecessors (nodes that are the result of node // cloning and that remains dandling around). bool Difference = true; while (Difference) { Difference = false; BasicBlockNode *Entry = &getEntryNode(); for (auto It = begin(); It != end(); It++) { if ((Entry != *It and (*It)->predecessor_size() == 0)) { removeNode(*It); Difference = true; break; } } } } template inline void RegionCFG::removeNotReachables(std::vector *> &MS) { // Remove nodes that have no predecessors (nodes that are the result of node // cloning and that remains dandling around). bool Difference = true; while (Difference) { Difference = false; BasicBlockNode *Entry = &getEntryNode(); for (auto It = begin(); It != end(); It++) { if ((Entry != *It and (*It)->predecessor_size() == 0)) { for (MetaRegion *M : MS) { M->removeNode(*It); } removeNode(*It); Difference = true; break; } } } } template inline bool RegionCFG::isDAG() { for (llvm::scc_iterator *> I = llvm::scc_begin(this), IE = llvm::scc_end(this); I != IE; ++I) if (I.hasCycle()) return false; return true; } template inline bool RegionCFG::isTopologicallyEquivalent(RegionCFG &Other) const { // The algorithm inspects in a depth first fashion the two graphs, and check // that they are topologically equivalent. Take care that this function may // return true if there are nodes not reachable from the entry node. // Early failure if the number of nodes composing the two CFG is different. if (size() != Other.size()) { return false; } // Retrieve the entry nodes of the two `RegionCFG` under analysis. BasicBlockNode &Entry = getEntryNode(); BasicBlockNode &OtherEntry = Other.getEntryNode(); // Check that the only node without predecessors is the entry node. for (const BasicBlockNode *Node : nodes()) { if (Node != &Entry and Node->predecessor_size() == 0) { return false; } } // Check that the only node without predecessors is the entry node. for (const BasicBlockNode *Node : Other.nodes()) { if (Node != &OtherEntry and Node->predecessor_size() == 0) { return false; } } // Call to a `BasicBlockNode` method which does a deep and recursive // comparison of a node and its successors. return Entry.isEquivalentTo(&OtherEntry); } template inline void RegionCFG::weave() { // Check that we are in a valid state of the graph. revng_assert(isDAG()); // Collect useful objects. RegionCFG &Graph = *this; IFPDT.recalculate(Graph); if (CombLogger.isEnabled()) { Graph.dumpCFGOnFile(FunctionName, "weave", "region-" + RegionName + "-before-weave"); } // Iterate over all the nodes in post order. BBNodeT *Entry = &getEntryNode(); for (BBNodeT *Switch : post_order(Entry)) { if (not Switch->isDispatcher() and not isASwitch(Switch)) continue; // If we find a switch node we can start the weaving analysis. if (Switch->successor_size() > 2) { revng_log(CombLogger, "Looking at switch node: " << Switch->getName() << "\n"); // Collect the case nodes of the switch. BasicBlockNodeTSet CaseSet; for (BBNodeT *Successor : Switch->successors()) CaseSet.insert(Successor); // Find the postdominator of the switch. BBNodeT *PostDom = IFPDT[Switch]->getIDom()->getBlock(); // Iterate over all the nodes "in the body" of the switch in reverse post // order. llvm::SmallPtrSet PostDomSet; if (nullptr != PostDom) PostDomSet.insert(PostDom); ReversePostOrderTraversalExt RPOT(EFGT(Switch), PostDomSet); revng_log(CombLogger, "Dumping the candidates that may initiate weaving:"); for (BBNodeT *RPOTBB : RPOT) { // We expect to never reach the PostDom revng_assert(RPOTBB != PostDom); // Skip the switch if (RPOTBB == Switch) continue; revng_log(CombLogger, RPOTBB->getName()); BasicBlockNodeTVect PostDominatedCases; for (BBNodeT *Case : CaseSet) if (IFPDT.dominates(RPOTBB, Case)) PostDominatedCases.push_back(Case); // Criterion to check if we need to perform the weaving. Specifically, // we need to perform a weaving if we find a node (between the switch // and its postdominator) that postdominates more than 1 of the cases. // Note: it cannot postdominate not all of them, otherwise it would be // the immediate postdominator of the switch, that we have explicitly // excluded. auto NumPostDominatedCases = PostDominatedCases.size(); revng_assert(NumPostDominatedCases != CaseSet.size()); if (NumPostDominatedCases > 1U) { // Create the new sub-switch node. BasicBlockNodeT *NewSwitch = nullptr; if (Switch->isDispatcher()) { NewSwitch = addDispatcher(Switch->getNameStr() + " weaved", Switch->getDispatcherType()); } else if (Switch->isCode()) { NewSwitch = addNode(Switch->getOriginalNode(), Switch->getNameStr() + " weaved"); } else { revng_unreachable("unexpected switch"); } revng_assert(nullptr != NewSwitch); revng_assert(not NewSwitch->successor_size()); NewSwitch->setWeaved(true); using edge_label_t = typename BasicBlockNodeT::edge_label_t; edge_label_t Labels; bool WeavingDefault = false; // Iterate over all the case nodes that we found, moving all the // necessary edges and updating the IFPDT. // Also, collect all the case labels of the cases we're weaving. for (BasicBlockNodeT *Case : PostDominatedCases) { auto LabeledEdge = extractLabeledEdge(EdgeDescriptor(Switch, Case)); IFPDT.deleteEdge(Switch, Case); auto &EdgeInfo = LabeledEdge.second; // If we find an edge without case labels, that's the default. if (EdgeInfo.Labels.empty()) { revng_assert(WeavingDefault == false); WeavingDefault = true; Labels = {}; } // If we're weaving the default, we don't care about the exact case // labels, because the weaved switch will become the default of the // original switch. if (not WeavingDefault) Labels.insert(EdgeInfo.Labels.begin(), EdgeInfo.Labels.end()); addEdge(EdgeDescriptor(NewSwitch, Case), EdgeInfo); IFPDT.insertEdge(NewSwitch, Case); CaseSet.erase(Case); } CaseSet.insert(NewSwitch); // Connect the old switch to the new one and update the IFPDT. // Use the collected labels to mark the new edge from the original // switch to the weaved switch. using EdgeInfo = typename BasicBlockNodeT::EdgeInfo; EdgeInfo EI = { Labels, false }; addEdge(EdgeDescriptor(Switch, NewSwitch), EI); IFPDT.insertEdge(Switch, NewSwitch); } } } } DT.recalculate(Graph); if (CombLogger.isEnabled()) { Graph.dumpCFGOnFile(FunctionName, "weave", "region-" + RegionName + "-after-weave"); } } template inline void RegionCFG::markUnreachableAsInlined() { llvm::SmallPtrSet UnreachableBlocks; for (BBNodeT *BBNode : *this) { if (not BBNode->isCode()) continue; llvm::BasicBlock *BB = BBNode->getOriginalNode(); if (llvm::isa(BB->getTerminator())) UnreachableBlocks.insert(BBNode); } for (BBNodeT *Unreachable : UnreachableBlocks) { BasicBlockNodeTVect Predecessors; for (BBNodeT *Pred : Unreachable->predecessors()) { Predecessors.push_back(Pred); markEdgeInlined(EdgeDescriptor(Pred, Unreachable)); } if (Predecessors.size() > 1) { for (BBNodeT *Pred : llvm::drop_begin(Predecessors, 1)) { BBNodeT *UnreachableClone = cloneNode(*Unreachable); moveEdgeTarget({ Pred, Unreachable }, UnreachableClone); } } } }