#pragma once // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include #include #include "llvm/ADT/BreadthFirstIterator.h" #include "llvm/ADT/DepthFirstIterator.h" #include "llvm/ADT/GraphTraits.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/SCCIterator.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SetOperations.h" #include "llvm/ADT/SetVector.h" #include "llvm/ADT/SmallPtrSet.h" #include "revng/Support/Debug.h" template struct scc_iterator_traits { using iterator = llvm::scc_iterator>; using iterator_category = std::forward_iterator_tag; using reference = decltype(*llvm::scc_begin((T) nullptr)); using value_type = std::remove_reference_t; using pointer = value_type *; using difference_type = size_t; }; template auto exitless_scc_range(NodeTy Entry) { using namespace llvm; auto Range = make_range(scc_begin(Entry), scc_end(Entry)); using NodesVector = std::vector; using GT = llvm::GraphTraits; auto Filter = [](const NodesVector &SCC) { std::set SCCNodes; SCCNodes.clear(); for (NodeTy BB : SCC) SCCNodes.insert(BB); bool HasExit = false; bool AtLeastOneEdge = false; for (NodeTy BB : SCC) { auto Successors = make_range(GT::child_begin(BB), GT::child_end(BB)); for (NodeTy Successor : Successors) { AtLeastOneEdge = true; if (!SCCNodes.contains(Successor)) { HasExit = true; break; } } if (HasExit) break; } return (not HasExit) and AtLeastOneEdge; }; return make_filter_range(Range, Filter); } /// A generic way to compute a set of entry points to a graph such that any node /// in said graph is reachable from at least one of those points. template requires std::is_pointer_v std::vector::NodeRef> entryPoints(GraphType &&Graph) { using NodeRef = typename llvm::GraphTraits::NodeRef; std::vector Result; // First, find all SCCs reachable from nodes without predecessors std::set Visited; for (const auto &Node : llvm::nodes(Graph)) { const auto &Preds = llvm::children>(Node); // If the Node has predecessors, skip it for now. It will be reached by a // visit from its predecessor. if (Preds.begin() != Preds.end()) continue; // Node has no predecessor, add it to Result. Result.push_back(Node); // Mark all the nodes reachable from it as Visited. for (const auto &Child : llvm::post_order_ext(NodeRef(Node), Visited)) ; } // At this point, everything in Visited is reachable from the "easy" entry // points, e.g. the nodes without predecessors that we have just detected // above. for (auto *Node : llvm::nodes(Graph)) { if (Visited.contains(Node)) continue; auto SCCBeg = llvm::scc_begin(NodeRef(Node)); auto SCCEnd = llvm::scc_end(NodeRef(Node)); // Ignore the case where there are no SCCs. if (SCCBeg == SCCEnd) continue; // Now we look only at the first SCC. We don't want to ever increment the // SCC iterator, because we want to only compute one SCC at a time, while // incrementing the SCC iterator computes the next SCC, possibly stepping // over stuff that has been Visited in the meantime. // For an example where this may happen, imagine the graph // A->B, B->A, B->C, C->D, D->C, where llvm::nodes visits D before A. // When visiting D, it would only see the SCC {C, D}, then when visiting A, // it would see the SCC {A, B} first, but it would recompute the SCC {C, D} // if incrementing the SCC iterator. This is something we want to avoid. const auto &TheSCC = *SCCBeg; const NodeRef &SCCEntryNode = TheSCC.front(); // If the initial node of the SCC is Visited, it means that the whole SCC // was visited by one of the previous iterations, so we just ignore it. if (Visited.contains(SCCEntryNode)) continue; // Then we mark all the nodes in the SCC as Visited, since we're visiting // them now. Visited.insert(TheSCC.begin(), TheSCC.end()); // Now, let's try to figure out if this SCC is reachable from outside. // If it is NOT, then we have to add the first element of the SCC to // Results. bool HasPredecessorOutsideSCC = false; for (const NodeRef &SCCNode : TheSCC) { for (auto *PredNode : llvm::inverse_children(SCCNode)) { if (llvm::find(TheSCC, PredNode) == TheSCC.end()) { HasPredecessorOutsideSCC = true; break; } } if (HasPredecessorOutsideSCC) break; } // If no element in TheSCC has a predecessor outside TheSCC we have to elect // an entry point for TheSCC. We just pick the first element since we have // no better clue about which entry would be best. if (not HasPredecessorOutsideSCC) Result.push_back(SCCEntryNode); } return Result; } template> auto graph_successors(GraphT Block) { return llvm::make_range(GT::child_begin(Block), GT::child_end(Block)); } template size_t graphSuccessorsSize(GraphT Block) { auto Range = graph_successors(Block); return std::distance(Range.begin(), Range.end()); } template auto graph_predecessors(GraphT Block) { using InverseGraphTraits = llvm::GraphTraits>; return graph_successors(Block); } template auto graphPredecessorsSize(GraphT Block) { auto Range = graph_predecessors(Block); return std::distance(Range.begin(), Range.end()); } namespace revng::detail { template using EdgeDescriptor = std::pair; enum FilterSet { WhiteList, BlackList }; template class DFStack { public: using InternalMapT = llvm::SmallMapVector; private: InternalMapT InternalMap; public: // Return the insertion iterator over the underlying map std::pair insertInMap(NodeT Block, bool OnStack) { return InternalMap.insert(std::make_pair(Block, OnStack)); } // Return true if b is currently on the active stack of visit bool onStack(NodeT Block) const { auto Iter = InternalMap.find(Block); return Iter != InternalMap.end() && Iter->second; } // Invoked after we have processed all children of a node during the DFS void completed(NodeT Block) { InternalMap[Block] = false; } InternalMapT::iterator begin() { return InternalMap.begin(); } InternalMapT::iterator end() { return InternalMap.end(); } }; template class DFStackBackedge { using InternalMapT = DFStack::InternalMapT; private: DFStack VisitStack; llvm::SmallPtrSet &Set; public: DFStackBackedge(llvm::SmallPtrSet &Set) : Set(Set) {} std::pair insert(NodeT Block) { // If we are trying to insert a valid block, we can process it as normal if ((FilterSetType == FilterSet::WhiteList and Set.contains(Block)) or (FilterSetType == FilterSet::BlackList and not Set.contains(Block))) { return VisitStack.insertInMap(Block, true); } else { // We want to completely ignore the fact that we inserted an element in // the `DFStack`, otherwise we will explore it anyway, therefore we // manually return `false`, so the node is not explored at all, in // addition of adding it as not on the exploration stack auto InsertIt = VisitStack.insertInMap(Block, false); return std::make_pair(InsertIt.first, false); } } bool onStack(NodeT Block) const { return VisitStack.onStack(Block); } void completed(NodeT Block) { return VisitStack.completed(Block); } }; template class DFStackReachable { using InternalMapT = DFStack::InternalMapT; template using SmallSetVector = llvm::SmallSetVector; private: DFStack VisitStack; private: // Set which contains the desired targets nodes marked as reachable during // the visit SmallSetVector Targets; llvm::SmallPtrSet &Set; llvm::SmallDenseMap, 4> AdditionalNodes; NodeT Source = nullptr; NodeT Target = nullptr; bool FirstInvocation = true; public: DFStackReachable(llvm::SmallPtrSet &Set) : Set(Set) {} // Insert the initial target node at the beginning of the visit void insertTarget(NodeT Block) { Targets.insert(Block); } // Assign the `Source` node void assignSource(NodeT Block) { Source = Block; } // Assign the `Target` node void assignTarget(NodeT Block) { Target = Block; } SmallSetVector getReachables() { return Targets; } SmallSetVector &getAdditional(NodeT Block) { return AdditionalNodes[Block]; } // Customize the `insert` method, in order to add the reachable nodes during // the DFS std::pair insert(NodeT Block) { if ((FilterSetType == FilterSet::WhiteList and Set.contains(Block)) or (FilterSetType == FilterSet::BlackList and not Set.contains(Block))) { // We need to insert the `Source` node, which is the first element on // which the `insert` method is called, only once, and later on skip it, // otherwise we may loop back from the `Source` and add additional nodes revng_assert(Source != nullptr); if (!FirstInvocation and Block == Source) { return VisitStack.insertInMap(Block, false); } FirstInvocation = false; // Check that, if we are trying to insert a block which is the `Targets` // set, we add all the nodes on the current visiting stack in the // `Targets` set auto SuccessorRange = graph_successors(Block); if (Targets.contains(Block) or (Target == nullptr and std::ranges::empty(SuccessorRange))) { for (auto const &[K, V] : VisitStack) { if (V) { Targets.insert(K); } } } // When we encounter a loop, we add to the additional set of nodes the // nodes that are onStack, for later additional post-processing if (VisitStack.onStack(Block)) { SmallSetVector &AdditionalSet = AdditionalNodes[Block]; for (auto const &[K, V] : VisitStack) { if (V) { AdditionalSet.insert(K); } } } // Return the insertion iterator as usual return VisitStack.insertInMap(Block, true); } else { // We want to completely ignore the fact that we inserted an element in // the `DFStack`, otherwise we will explore it anyway, therefore we // manually return `false`, so the node is not explored at all, in // addition of adding it as not on the exploration stack auto InsertIt = VisitStack.insertInMap(Block, false); return std::make_pair(InsertIt.first, false); } } std::pair insertInMap(NodeT Block, bool OnStack) { return VisitStack.insertInMap(Block, OnStack); } void completed(NodeT Block) { return VisitStack.completed(Block); } }; } // namespace revng::detail // TODO: remove the `BlackList`/`WhiteList` parameter once the last use in // `revng-c` is dropped, checking that no other uses are still present template> llvm::SmallSetVector, 4> getBackedgesImpl(GraphT Block, llvm::SmallPtrSet &Set) { using NodeRef = typename GT::NodeRef; using StateType = typename revng::detail::DFStackBackedge; using EdgeDescriptor = revng::detail::EdgeDescriptor; StateType State(Set); llvm::SmallSetVector Backedges; // Declare manually a custom `df_iterator` using bdf_iterator = llvm::df_iterator; auto Begin = bdf_iterator::begin(Block, State); auto End = bdf_iterator::end(Block, State); for (NodeRef Block : llvm::make_range(Begin, End)) { for (NodeRef Succ : llvm::make_range(GT::child_begin(Block), GT::child_end(Block))) { if (State.onStack(Succ)) { Backedges.insert(EdgeDescriptor(Block, Succ)); } } } return Backedges; } template> llvm::SmallSetVector, 4> getBackedges(GraphT Block) { llvm::SmallPtrSet EmptySet; return getBackedgesImpl(Block, EmptySet); } template> llvm::SmallSetVector, 4> getBackedgesWhiteList(GraphT Block, llvm::SmallPtrSet &Set) { return getBackedgesImpl(Block, Set); } template> llvm::SmallSetVector, 4> getBackedgesBlackList(GraphT Block, llvm::SmallPtrSet &Set) { return getBackedgesImpl(Block, Set); } // TODO: remove the `BlackList`/`WhiteList` parameter once the new AVI // implementation is merged, checking that no other uses are still present template> llvm::SmallSetVector nodesBetweenImpl(GraphT Source, GraphT Target, llvm::SmallPtrSet &Set) { using NodeRef = typename GT::NodeRef; using StateType = typename revng::detail::DFStackReachable; StateType State(Set); using SmallSetVector = llvm::SmallSetVector; // Assign the `Source` node State.assignSource(Source); // Initialize the visited set with the target node, which is the boundary // that we don't want to trepass when finding reachable nodes State.assignTarget(Target); State.insertTarget(Target); State.insertInMap(Target, false); using nbdf_iterator = llvm::df_iterator; auto Begin = nbdf_iterator::begin(Source, State); auto End = nbdf_iterator::end(Source, State); for (NodeRef Block : llvm::make_range(Begin, End)) { (void) Block; } auto Targets = State.getReachables(); // Add in a fixed point fashion the additional nodes SmallSetVector OldTargets; do { // At each iteration obtain a copy of the old set, so that we are able to // exit from the loop as soon no change is made to the `Targets` set OldTargets = Targets; // Temporary storage for the nodes to add at each iteration, to avoid // invalidation on the `Targets` set SmallSetVector NodesToAdd; for (NodeRef Block : Targets) { SmallSetVector &AdditionalSet = State.getAdditional(Block); NodesToAdd.insert(AdditionalSet.begin(), AdditionalSet.end()); } // Add all the additional nodes found in this step Targets.insert(NodesToAdd.begin(), NodesToAdd.end()); NodesToAdd.clear(); } while (Targets != OldTargets); return Targets; } template inline llvm::SmallSetVector nodesBetween(GraphT Source, GraphT Destination) { llvm::SmallPtrSet::NodeRef, 4> EmptySet; return nodesBetweenImpl>(Source, Destination, EmptySet); } template::NodeRef> inline llvm::SmallSetVector nodesBetweenWhiteList(GraphT Source, GraphT Destination, llvm::SmallPtrSet &Set) { return nodesBetweenImpl>(Source, Destination, Set); } template::NodeRef> inline llvm::SmallSetVector nodesBetweenBlackList(GraphT Source, GraphT Destination, llvm::SmallPtrSet &Set) { return nodesBetweenImpl>(Source, Destination, Set); } template inline llvm::SmallSetVector nodesBetweenReverse(GraphT Source, GraphT Destination) { using namespace llvm; llvm::SmallPtrSet::NodeRef, 4> EmptySet; return nodesBetweenImpl>>(Source, Destination, EmptySet); } template< class GraphT, typename NodeRef = typename llvm::GraphTraits>::NodeRef> inline llvm::SmallSetVector nodesBetweenReverseWhiteList(GraphT Source, GraphT Destination, llvm::SmallPtrSet &Set) { using namespace llvm; return nodesBetweenImpl>>(Source, Destination, Set); } template::NodeRef> inline llvm::SmallSetVector nodesBetweenReverseBlackList(GraphT Source, GraphT Destination, llvm::SmallPtrSet &Set) { using namespace llvm; return nodesBetweenImpl>>(Source, Destination, Set); } template bool isDAG(GraphT Graph) { for (llvm::scc_iterator I = llvm::scc_begin(Graph), IE = llvm::scc_end(Graph); I != IE; ++I) { if (I.hasCycle()) return false; } return true; }