Files
revng-revng/include/revng/Support/GraphAlgorithms.h
Alessandro Di Federico 686083bdab Remove mentions to revng-c
2024-11-21 15:54:23 +01:00

451 lines
15 KiB
C++

#pragma once
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include <algorithm>
#include <ranges>
#include <set>
#include <vector>
#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<typename T>
struct scc_iterator_traits {
using iterator = llvm::scc_iterator<T, llvm::GraphTraits<T>>;
using iterator_category = std::forward_iterator_tag;
using reference = decltype(*llvm::scc_begin((T) nullptr));
using value_type = std::remove_reference_t<reference>;
using pointer = value_type *;
using difference_type = size_t;
};
template<typename NodeTy>
auto exitless_scc_range(NodeTy Entry) {
using namespace llvm;
auto Range = make_range(scc_begin(Entry), scc_end(Entry));
using NodesVector = std::vector<NodeTy>;
using GT = llvm::GraphTraits<NodeTy>;
auto Filter = [](const NodesVector &SCC) {
std::set<NodeTy> 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<typename GraphType>
requires std::is_pointer_v<GraphType>
std::vector<typename llvm::GraphTraits<GraphType>::NodeRef>
entryPoints(GraphType &&Graph) {
using NodeRef = typename llvm::GraphTraits<GraphType>::NodeRef;
std::vector<NodeRef> Result;
// First, find all SCCs reachable from nodes without predecessors
std::set<const NodeRef> Visited;
for (const auto &Node : llvm::nodes(Graph)) {
const auto &Preds = llvm::children<llvm::Inverse<NodeRef>>(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<NodeRef>(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<class GraphT, class GT = llvm::GraphTraits<GraphT>>
auto graph_successors(GraphT Block) {
return llvm::make_range(GT::child_begin(Block), GT::child_end(Block));
}
template<class GraphT>
size_t graphSuccessorsSize(GraphT Block) {
auto Range = graph_successors(Block);
return std::distance(Range.begin(), Range.end());
}
template<class GraphT>
auto graph_predecessors(GraphT Block) {
using InverseGraphTraits = llvm::GraphTraits<llvm::Inverse<GraphT>>;
return graph_successors<GraphT, InverseGraphTraits>(Block);
}
template<class GraphT>
auto graphPredecessorsSize(GraphT Block) {
auto Range = graph_predecessors(Block);
return std::distance(Range.begin(), Range.end());
}
namespace revng::detail {
template<class NodeT>
using EdgeDescriptor = std::pair<NodeT, NodeT>;
enum FilterSet {
WhiteList,
BlackList
};
template<class NodeT>
class DFStack {
public:
using InternalMapT = llvm::SmallMapVector<NodeT, bool, 4>;
private:
InternalMapT InternalMap;
public:
// Return the insertion iterator over the underlying map
std::pair<typename InternalMapT::iterator, bool> insertInMap(NodeT Block,
bool OnStack) {
return InternalMap.insert(std::make_pair(Block, OnStack));
}
// Return true if Block 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<FilterSet FilterSetType, class NodeT>
class DFStackBackedge {
using InternalMapT = DFStack<NodeT>::InternalMapT;
private:
DFStack<NodeT> VisitStack;
llvm::SmallPtrSet<NodeT, 4> &Set;
public:
DFStackBackedge(llvm::SmallPtrSet<NodeT, 4> &Set) : Set(Set) {}
std::pair<typename InternalMapT::iterator, bool> 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); }
};
} // namespace revng::detail
// TODO: remove the `BlackList`/`WhiteList` parameter once the last use is
// dropped, checking that no other uses are still present
template<revng::detail::FilterSet FilterSetType,
class GraphT,
class GT = llvm::GraphTraits<GraphT>>
llvm::SmallSetVector<revng::detail::EdgeDescriptor<typename GT::NodeRef>, 4>
getBackedgesImpl(GraphT Block,
llvm::SmallPtrSet<typename GT::NodeRef, 4> &Set) {
using NodeRef = typename GT::NodeRef;
using StateType = typename revng::detail::DFStackBackedge<FilterSetType,
NodeRef>;
using EdgeDescriptor = revng::detail::EdgeDescriptor<NodeRef>;
StateType State(Set);
llvm::SmallSetVector<EdgeDescriptor, 4> Backedges;
// Declare manually a custom `df_iterator`
using bdf_iterator = llvm::df_iterator<GraphT, StateType, true, GT>;
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<class GraphT, class GT = llvm::GraphTraits<GraphT>>
llvm::SmallSetVector<revng::detail::EdgeDescriptor<typename GT::NodeRef>, 4>
getBackedges(GraphT Block) {
llvm::SmallPtrSet<typename GT::NodeRef, 4> EmptySet;
return getBackedgesImpl<revng::detail::FilterSet::BlackList>(Block, EmptySet);
}
template<class GraphT, class GT = llvm::GraphTraits<GraphT>>
llvm::SmallSetVector<revng::detail::EdgeDescriptor<typename GT::NodeRef>, 4>
getBackedgesWhiteList(GraphT Block,
llvm::SmallPtrSet<typename GT::NodeRef, 4> &Set) {
return getBackedgesImpl<revng::detail::FilterSet::WhiteList>(Block, Set);
}
template<class GraphT, class GT = llvm::GraphTraits<GraphT>>
llvm::SmallSetVector<revng::detail::EdgeDescriptor<typename GT::NodeRef>, 4>
getBackedgesBlackList(GraphT Block,
llvm::SmallPtrSet<typename GT::NodeRef, 4> &Set) {
return getBackedgesImpl<revng::detail::FilterSet::BlackList>(Block, Set);
}
template<class GraphT, class GT>
bool hasSuccessor(GraphT Pred, GraphT Succ) {
for (auto Child :
llvm::make_range(GT::child_begin(Pred), GT::child_end(Pred))) {
if (Child == Succ) {
return true;
}
}
return false;
}
/// The `findReachableNodes` primitive returns the set of nodes reachable from
/// the `Source` node, stopping at the `Stop` node.
/// \param Source The node where the DFS starts from
/// \param Stop The node where the DFS should stop, if present
/// \return Note that the return value is a `SmallSetVector`, so we have
/// additional guarantees about the deterministic iteration order over
/// the returned set
template<class GraphT, class GT>
llvm::SmallSetVector<typename llvm::GraphTraits<GraphT>::NodeRef, 4>
findReachableNodesImpl(GraphT Source, GraphT Stop = nullptr) {
using NodeRef = typename GT::NodeRef;
using SmallSetVector = llvm::SmallSetVector<NodeRef, 4>;
SmallSetVector DFSNodes;
// Verify that the `Source` node is valid
revng_assert(Source != nullptr);
// Perform the forward DFS visit, stopping at the `Stop` node, if present
using ExtType = typename llvm::df_iterator_default_set<NodeRef>;
ExtType ExtSet;
if (Stop != nullptr) {
ExtSet.insert(Stop);
}
using df_iterator = llvm::df_iterator<GraphT, ExtType, false, GT>;
auto Begin = df_iterator::begin(Source, ExtSet);
auto End = df_iterator::end(Source, ExtSet);
for (NodeRef Block : llvm::make_range(Begin, End)) {
DFSNodes.insert(Block);
}
return DFSNodes;
}
template<class GraphT, class GT = llvm::GraphTraits<GraphT>>
inline llvm::SmallSetVector<GraphT, 4>
findReachableNodes(GraphT Source, GraphT Stop = nullptr) {
return findReachableNodesImpl<GraphT, GT>(Source, Stop);
}
/// The `nodesBetween` primitive, collects all the nodes present on any path
/// that connects the `Source` node and the `Target` node.
/// \return Note that the return value is a `SmallSetVector`, so we have
/// additional guarantees about the deterministic iteration order over
/// the returned set
template<class GraphT, class GraphDirection>
llvm::SmallSetVector<typename llvm::GraphTraits<GraphT>::NodeRef, 4>
nodesBetweenImpl(GraphT Source, GraphT Target) {
using FGT = llvm::GraphTraits<GraphDirection>;
using BGT = llvm::GraphTraits<llvm::Inverse<GraphDirection>>;
using NodeRef = typename FGT::NodeRef;
using SmallSetVector = llvm::SmallSetVector<NodeRef, 4>;
// We cannot perform a `nodesBetween` search either starting from a null
// `Source` or ending in a null `Target`
revng_assert(Source != nullptr);
revng_assert(Target != nullptr);
// Perform the forward DFS visit, stopping at the `Target` node
SmallSetVector ForwardDFSNodes = findReachableNodes<NodeRef, FGT>(Source,
Target);
// Perform the backward DFS visit, stopping at the `Source` node
SmallSetVector BackwardDFSNodes = findReachableNodes<NodeRef, BGT>(Target,
Source);
// Perform the set intersection between the forward and backward set nodes
SmallSetVector Result = llvm::set_intersection(ForwardDFSNodes,
BackwardDFSNodes);
// Corner case handling
if (not Result.empty()) {
// We found some nodes in the intersection of the two DFSs, therefore we
// actually have some reachables nodes, and we add both the `Source` and the
// `Target` in the `Result` set (they are not automatically included because
// the are part of the respective ext sets, and therefore not present in the
// intersection)
Result.insert(Target);
Result.insert(Source);
} else if (hasSuccessor<GraphT, FGT>(Source, Target)) {
// If the `Target` is a direct successor of `Source`, we may have that the
// resulting intersection is empty, if the only path connecting them is the
// trivial one, but the reachable nodes are actually `Source` and `Target`
revng_assert(Result.empty());
Result.insert(Target);
Result.insert(Source);
} else if (Source == Target) {
// If `Source` is equal to `Target`, due to fact that `Target` (which is `==
// Source`) is added to the ext set, (and vice-versa), we will not find any
// candidate note in our exploration, therefore we need to manually add it
// to the `Result` set
Result.insert(Source);
}
// The only situation where we can have a `Result` set of dimension 1, is when
// `Source` == `Target`.
revng_assert(Source == Target or Result.size() != 1);
return Result;
}
template<class GraphT>
inline llvm::SmallSetVector<GraphT, 4>
nodesBetween(GraphT Source, GraphT Destination) {
return nodesBetweenImpl<GraphT, GraphT>(Source, Destination);
}
template<class GraphT>
inline llvm::SmallSetVector<GraphT, 4>
nodesBetweenReverse(GraphT Source, GraphT Destination) {
using namespace llvm;
return nodesBetweenImpl<GraphT, Inverse<GraphT>>(Source, Destination);
}
template<class GraphT>
bool isDAG(GraphT Graph) {
for (llvm::scc_iterator<GraphT> I = llvm::scc_begin(Graph),
IE = llvm::scc_end(Graph);
I != IE;
++I) {
if (I.hasCycle())
return false;
}
return true;
}