mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
e2090b8746
We now implement the `nodesBetween` primitive with a double DFS visit. Specifically, we have: 1) A forward DFS that starts from the `Source`, and stops at `Target`. 2) A backward DFS that starts from the `Target`, and stops at `Source`. The final result is then the intersection of the nodes found by the two above DFS visits. A new `findReachableNodes` primitive is also added to perform a DFS from a `Source` node, which stops at the `Stop` parameter node, if present. Added unit tests to improve the coverage.
451 lines
15 KiB
C++
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 in
|
|
// `revng-c` 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;
|
|
}
|