| //==========-- ImmutableGraph.h - A fast DAG implementation ---------=========// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| /// \file |
| /// Description: ImmutableGraph is a fast DAG implementation that cannot be |
| /// modified, except by creating a new ImmutableGraph. ImmutableGraph is |
| /// implemented as two arrays: one containing nodes, and one containing edges. |
| /// The advantages to this implementation are two-fold: |
| /// 1. Iteration and traversal operations benefit from cache locality. |
| /// 2. Operations on sets of nodes/edges are efficient, and representations of |
| /// those sets in memory are compact. For instance, a set of edges is |
| /// implemented as a bit vector, wherein each bit corresponds to one edge in |
| /// the edge array. This implies a lower bound of 64x spatial improvement |
| /// over, e.g., an llvm::DenseSet or llvm::SmallSet. It also means that |
| /// insert/erase/contains operations complete in negligible constant time: |
| /// insert and erase require one load and one store, and contains requires |
| /// just one load. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef LLVM_LIB_TARGET_X86_IMMUTABLEGRAPH_H |
| #define LLVM_LIB_TARGET_X86_IMMUTABLEGRAPH_H |
| |
| #include "llvm/ADT/BitVector.h" |
| #include "llvm/ADT/GraphTraits.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include <algorithm> |
| #include <iterator> |
| #include <utility> |
| #include <vector> |
| |
| namespace llvm { |
| |
| template <typename NodeValueT, typename EdgeValueT> class ImmutableGraph { |
| using Traits = GraphTraits<ImmutableGraph<NodeValueT, EdgeValueT> *>; |
| template <typename> friend class ImmutableGraphBuilder; |
| |
| public: |
| using node_value_type = NodeValueT; |
| using edge_value_type = EdgeValueT; |
| using size_type = int; |
| class Node; |
| class Edge { |
| friend class ImmutableGraph; |
| template <typename> friend class ImmutableGraphBuilder; |
| |
| const Node *Dest; |
| edge_value_type Value; |
| |
| public: |
| const Node *getDest() const { return Dest; }; |
| const edge_value_type &getValue() const { return Value; } |
| }; |
| class Node { |
| friend class ImmutableGraph; |
| template <typename> friend class ImmutableGraphBuilder; |
| |
| const Edge *Edges; |
| node_value_type Value; |
| |
| public: |
| const node_value_type &getValue() const { return Value; } |
| |
| const Edge *edges_begin() const { return Edges; } |
| // Nodes are allocated sequentially. Edges for a node are stored together. |
| // The end of this Node's edges is the beginning of the next node's edges. |
| // An extra node was allocated to hold the end pointer for the last real |
| // node. |
| const Edge *edges_end() const { return (this + 1)->Edges; } |
| ArrayRef<Edge> edges() const { |
| return makeArrayRef(edges_begin(), edges_end()); |
| } |
| }; |
| |
| protected: |
| ImmutableGraph(std::unique_ptr<Node[]> Nodes, std::unique_ptr<Edge[]> Edges, |
| size_type NodesSize, size_type EdgesSize) |
| : Nodes(std::move(Nodes)), Edges(std::move(Edges)), NodesSize(NodesSize), |
| EdgesSize(EdgesSize) {} |
| ImmutableGraph(const ImmutableGraph &) = delete; |
| ImmutableGraph(ImmutableGraph &&) = delete; |
| ImmutableGraph &operator=(const ImmutableGraph &) = delete; |
| ImmutableGraph &operator=(ImmutableGraph &&) = delete; |
| |
| public: |
| ArrayRef<Node> nodes() const { return makeArrayRef(Nodes.get(), NodesSize); } |
| const Node *nodes_begin() const { return nodes().begin(); } |
| const Node *nodes_end() const { return nodes().end(); } |
| |
| ArrayRef<Edge> edges() const { return makeArrayRef(Edges.get(), EdgesSize); } |
| const Edge *edges_begin() const { return edges().begin(); } |
| const Edge *edges_end() const { return edges().end(); } |
| |
| size_type nodes_size() const { return NodesSize; } |
| size_type edges_size() const { return EdgesSize; } |
| |
| // Node N must belong to this ImmutableGraph. |
| size_type getNodeIndex(const Node &N) const { |
| return std::distance(nodes_begin(), &N); |
| } |
| // Edge E must belong to this ImmutableGraph. |
| size_type getEdgeIndex(const Edge &E) const { |
| return std::distance(edges_begin(), &E); |
| } |
| |
| // FIXME: Could NodeSet and EdgeSet be templated to share code? |
| class NodeSet { |
| const ImmutableGraph &G; |
| BitVector V; |
| |
| public: |
| NodeSet(const ImmutableGraph &G, bool ContainsAll = false) |
| : G{G}, V{static_cast<unsigned>(G.nodes_size()), ContainsAll} {} |
| bool insert(const Node &N) { |
| size_type Idx = G.getNodeIndex(N); |
| bool AlreadyExists = V.test(Idx); |
| V.set(Idx); |
| return !AlreadyExists; |
| } |
| void erase(const Node &N) { |
| size_type Idx = G.getNodeIndex(N); |
| V.reset(Idx); |
| } |
| bool contains(const Node &N) const { |
| size_type Idx = G.getNodeIndex(N); |
| return V.test(Idx); |
| } |
| void clear() { V.reset(); } |
| size_type empty() const { return V.none(); } |
| /// Return the number of elements in the set |
| size_type count() const { return V.count(); } |
| /// Return the size of the set's domain |
| size_type size() const { return V.size(); } |
| /// Set union |
| NodeSet &operator|=(const NodeSet &RHS) { |
| assert(&this->G == &RHS.G); |
| V |= RHS.V; |
| return *this; |
| } |
| /// Set intersection |
| NodeSet &operator&=(const NodeSet &RHS) { |
| assert(&this->G == &RHS.G); |
| V &= RHS.V; |
| return *this; |
| } |
| /// Set disjoint union |
| NodeSet &operator^=(const NodeSet &RHS) { |
| assert(&this->G == &RHS.G); |
| V ^= RHS.V; |
| return *this; |
| } |
| |
| using index_iterator = typename BitVector::const_set_bits_iterator; |
| index_iterator index_begin() const { return V.set_bits_begin(); } |
| index_iterator index_end() const { return V.set_bits_end(); } |
| void set(size_type Idx) { V.set(Idx); } |
| void reset(size_type Idx) { V.reset(Idx); } |
| |
| class iterator { |
| const NodeSet &Set; |
| size_type Current; |
| |
| void advance() { |
| assert(Current != -1); |
| Current = Set.V.find_next(Current); |
| } |
| |
| public: |
| iterator(const NodeSet &Set, size_type Begin) |
| : Set{Set}, Current{Begin} {} |
| iterator operator++(int) { |
| iterator Tmp = *this; |
| advance(); |
| return Tmp; |
| } |
| iterator &operator++() { |
| advance(); |
| return *this; |
| } |
| Node *operator*() const { |
| assert(Current != -1); |
| return Set.G.nodes_begin() + Current; |
| } |
| bool operator==(const iterator &other) const { |
| assert(&this->Set == &other.Set); |
| return this->Current == other.Current; |
| } |
| bool operator!=(const iterator &other) const { return !(*this == other); } |
| }; |
| |
| iterator begin() const { return iterator{*this, V.find_first()}; } |
| iterator end() const { return iterator{*this, -1}; } |
| }; |
| |
| class EdgeSet { |
| const ImmutableGraph &G; |
| BitVector V; |
| |
| public: |
| EdgeSet(const ImmutableGraph &G, bool ContainsAll = false) |
| : G{G}, V{static_cast<unsigned>(G.edges_size()), ContainsAll} {} |
| bool insert(const Edge &E) { |
| size_type Idx = G.getEdgeIndex(E); |
| bool AlreadyExists = V.test(Idx); |
| V.set(Idx); |
| return !AlreadyExists; |
| } |
| void erase(const Edge &E) { |
| size_type Idx = G.getEdgeIndex(E); |
| V.reset(Idx); |
| } |
| bool contains(const Edge &E) const { |
| size_type Idx = G.getEdgeIndex(E); |
| return V.test(Idx); |
| } |
| void clear() { V.reset(); } |
| bool empty() const { return V.none(); } |
| /// Return the number of elements in the set |
| size_type count() const { return V.count(); } |
| /// Return the size of the set's domain |
| size_type size() const { return V.size(); } |
| /// Set union |
| EdgeSet &operator|=(const EdgeSet &RHS) { |
| assert(&this->G == &RHS.G); |
| V |= RHS.V; |
| return *this; |
| } |
| /// Set intersection |
| EdgeSet &operator&=(const EdgeSet &RHS) { |
| assert(&this->G == &RHS.G); |
| V &= RHS.V; |
| return *this; |
| } |
| /// Set disjoint union |
| EdgeSet &operator^=(const EdgeSet &RHS) { |
| assert(&this->G == &RHS.G); |
| V ^= RHS.V; |
| return *this; |
| } |
| |
| using index_iterator = typename BitVector::const_set_bits_iterator; |
| index_iterator index_begin() const { return V.set_bits_begin(); } |
| index_iterator index_end() const { return V.set_bits_end(); } |
| void set(size_type Idx) { V.set(Idx); } |
| void reset(size_type Idx) { V.reset(Idx); } |
| |
| class iterator { |
| const EdgeSet &Set; |
| size_type Current; |
| |
| void advance() { |
| assert(Current != -1); |
| Current = Set.V.find_next(Current); |
| } |
| |
| public: |
| iterator(const EdgeSet &Set, size_type Begin) |
| : Set{Set}, Current{Begin} {} |
| iterator operator++(int) { |
| iterator Tmp = *this; |
| advance(); |
| return Tmp; |
| } |
| iterator &operator++() { |
| advance(); |
| return *this; |
| } |
| Edge *operator*() const { |
| assert(Current != -1); |
| return Set.G.edges_begin() + Current; |
| } |
| bool operator==(const iterator &other) const { |
| assert(&this->Set == &other.Set); |
| return this->Current == other.Current; |
| } |
| bool operator!=(const iterator &other) const { return !(*this == other); } |
| }; |
| |
| iterator begin() const { return iterator{*this, V.find_first()}; } |
| iterator end() const { return iterator{*this, -1}; } |
| }; |
| |
| private: |
| std::unique_ptr<Node[]> Nodes; |
| std::unique_ptr<Edge[]> Edges; |
| size_type NodesSize; |
| size_type EdgesSize; |
| }; |
| |
| template <typename GraphT> class ImmutableGraphBuilder { |
| using node_value_type = typename GraphT::node_value_type; |
| using edge_value_type = typename GraphT::edge_value_type; |
| static_assert( |
| std::is_base_of<ImmutableGraph<node_value_type, edge_value_type>, |
| GraphT>::value, |
| "Template argument to ImmutableGraphBuilder must derive from " |
| "ImmutableGraph<>"); |
| using size_type = typename GraphT::size_type; |
| using NodeSet = typename GraphT::NodeSet; |
| using Node = typename GraphT::Node; |
| using EdgeSet = typename GraphT::EdgeSet; |
| using Edge = typename GraphT::Edge; |
| using BuilderEdge = std::pair<edge_value_type, size_type>; |
| using EdgeList = std::vector<BuilderEdge>; |
| using BuilderVertex = std::pair<node_value_type, EdgeList>; |
| using VertexVec = std::vector<BuilderVertex>; |
| |
| public: |
| using BuilderNodeRef = size_type; |
| |
| BuilderNodeRef addVertex(const node_value_type &V) { |
| auto I = AdjList.emplace(AdjList.end(), V, EdgeList{}); |
| return std::distance(AdjList.begin(), I); |
| } |
| |
| void addEdge(const edge_value_type &E, BuilderNodeRef From, |
| BuilderNodeRef To) { |
| AdjList[From].second.emplace_back(E, To); |
| } |
| |
| bool empty() const { return AdjList.empty(); } |
| |
| template <typename... ArgT> std::unique_ptr<GraphT> get(ArgT &&... Args) { |
| size_type VertexSize = AdjList.size(), EdgeSize = 0; |
| for (const auto &V : AdjList) { |
| EdgeSize += V.second.size(); |
| } |
| auto VertexArray = |
| std::make_unique<Node[]>(VertexSize + 1 /* terminator node */); |
| auto EdgeArray = std::make_unique<Edge[]>(EdgeSize); |
| size_type VI = 0, EI = 0; |
| for (; VI < VertexSize; ++VI) { |
| VertexArray[VI].Value = std::move(AdjList[VI].first); |
| VertexArray[VI].Edges = &EdgeArray[EI]; |
| auto NumEdges = static_cast<size_type>(AdjList[VI].second.size()); |
| for (size_type VEI = 0; VEI < NumEdges; ++VEI, ++EI) { |
| auto &E = AdjList[VI].second[VEI]; |
| EdgeArray[EI].Value = std::move(E.first); |
| EdgeArray[EI].Dest = &VertexArray[E.second]; |
| } |
| } |
| assert(VI == VertexSize && EI == EdgeSize && "ImmutableGraph malformed"); |
| VertexArray[VI].Edges = &EdgeArray[EdgeSize]; // terminator node |
| return std::make_unique<GraphT>(std::move(VertexArray), |
| std::move(EdgeArray), VertexSize, EdgeSize, |
| std::forward<ArgT>(Args)...); |
| } |
| |
| template <typename... ArgT> |
| static std::unique_ptr<GraphT> trim(const GraphT &G, const NodeSet &TrimNodes, |
| const EdgeSet &TrimEdges, |
| ArgT &&... Args) { |
| size_type NewVertexSize = G.nodes_size() - TrimNodes.count(); |
| size_type NewEdgeSize = G.edges_size() - TrimEdges.count(); |
| auto NewVertexArray = |
| std::make_unique<Node[]>(NewVertexSize + 1 /* terminator node */); |
| auto NewEdgeArray = std::make_unique<Edge[]>(NewEdgeSize); |
| |
| // Walk the nodes and determine the new index for each node. |
| size_type NewNodeIndex = 0; |
| std::vector<size_type> RemappedNodeIndex(G.nodes_size()); |
| for (const Node &N : G.nodes()) { |
| if (TrimNodes.contains(N)) |
| continue; |
| RemappedNodeIndex[G.getNodeIndex(N)] = NewNodeIndex++; |
| } |
| assert(NewNodeIndex == NewVertexSize && |
| "Should have assigned NewVertexSize indices"); |
| |
| size_type VertexI = 0, EdgeI = 0; |
| for (const Node &N : G.nodes()) { |
| if (TrimNodes.contains(N)) |
| continue; |
| NewVertexArray[VertexI].Value = N.getValue(); |
| NewVertexArray[VertexI].Edges = &NewEdgeArray[EdgeI]; |
| for (const Edge &E : N.edges()) { |
| if (TrimEdges.contains(E)) |
| continue; |
| NewEdgeArray[EdgeI].Value = E.getValue(); |
| size_type DestIdx = G.getNodeIndex(*E.getDest()); |
| size_type NewIdx = RemappedNodeIndex[DestIdx]; |
| assert(NewIdx < NewVertexSize); |
| NewEdgeArray[EdgeI].Dest = &NewVertexArray[NewIdx]; |
| ++EdgeI; |
| } |
| ++VertexI; |
| } |
| assert(VertexI == NewVertexSize && EdgeI == NewEdgeSize && |
| "Gadget graph malformed"); |
| NewVertexArray[VertexI].Edges = &NewEdgeArray[NewEdgeSize]; // terminator |
| return std::make_unique<GraphT>(std::move(NewVertexArray), |
| std::move(NewEdgeArray), NewVertexSize, |
| NewEdgeSize, std::forward<ArgT>(Args)...); |
| } |
| |
| private: |
| VertexVec AdjList; |
| }; |
| |
| template <typename NodeValueT, typename EdgeValueT> |
| struct GraphTraits<ImmutableGraph<NodeValueT, EdgeValueT> *> { |
| using GraphT = ImmutableGraph<NodeValueT, EdgeValueT>; |
| using NodeRef = typename GraphT::Node const *; |
| using EdgeRef = typename GraphT::Edge const &; |
| |
| static NodeRef edge_dest(EdgeRef E) { return E.getDest(); } |
| using ChildIteratorType = |
| mapped_iterator<typename GraphT::Edge const *, decltype(&edge_dest)>; |
| |
| static NodeRef getEntryNode(GraphT *G) { return G->nodes_begin(); } |
| static ChildIteratorType child_begin(NodeRef N) { |
| return {N->edges_begin(), &edge_dest}; |
| } |
| static ChildIteratorType child_end(NodeRef N) { |
| return {N->edges_end(), &edge_dest}; |
| } |
| |
| static NodeRef getNode(typename GraphT::Node const &N) { return NodeRef{&N}; } |
| using nodes_iterator = |
| mapped_iterator<typename GraphT::Node const *, decltype(&getNode)>; |
| static nodes_iterator nodes_begin(GraphT *G) { |
| return {G->nodes_begin(), &getNode}; |
| } |
| static nodes_iterator nodes_end(GraphT *G) { |
| return {G->nodes_end(), &getNode}; |
| } |
| |
| using ChildEdgeIteratorType = typename GraphT::Edge const *; |
| |
| static ChildEdgeIteratorType child_edge_begin(NodeRef N) { |
| return N->edges_begin(); |
| } |
| static ChildEdgeIteratorType child_edge_end(NodeRef N) { |
| return N->edges_end(); |
| } |
| static typename GraphT::size_type size(GraphT *G) { return G->nodes_size(); } |
| }; |
| |
| } // end namespace llvm |
| |
| #endif // LLVM_LIB_TARGET_X86_IMMUTABLEGRAPH_H |