304 lines
12 KiB
C++
304 lines
12 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Pairing Heap data structure
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// Copyright 2003-2025 by Wilson Snyder. This program is free software; you
|
|
// can redistribute it and/or modify it under the terms of either the GNU
|
|
// Lesser General Public License Version 3 or the Perl Artistic License
|
|
// Version 2.0.
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
//
|
|
//*************************************************************************
|
|
|
|
#ifndef VERILATOR_V3PAIRINGHEAP_H_
|
|
#define VERILATOR_V3PAIRINGHEAP_H_
|
|
|
|
#include "config_build.h"
|
|
#include "verilatedos.h"
|
|
|
|
#include "V3Error.h"
|
|
|
|
//=============================================================================
|
|
// Pairing heap (max-heap) with increase key and delete.
|
|
//
|
|
// While this is written as a generic data structure, it's interface and
|
|
// implementation is finely tuned for use by V3Partition, and is critical
|
|
// to Verilation performance, so be very careful changing anything or adding any
|
|
// new operations that would impact either memory usage, or performance of the
|
|
// existing operations. This data structure is fully deterministic, meaning
|
|
// the order in which elements with equal keys are retrieved only depends on
|
|
// the order of operations performed on the heap.
|
|
//=============================================================================
|
|
|
|
template <typename T_Key>
|
|
class PairingHeap final {
|
|
public:
|
|
struct Node;
|
|
|
|
// Just a pointer to a heap Node, but with special accessors to help keep back pointers
|
|
// consistent.
|
|
struct Link final {
|
|
Node* m_ptr = nullptr; // The managed pointer
|
|
|
|
Link() = default;
|
|
VL_UNCOPYABLE(Link);
|
|
|
|
// Make the pointer point to the target, and the target's owner pointer to this pointer
|
|
VL_ATTR_ALWINLINE void link(Node* targetp) {
|
|
m_ptr = targetp;
|
|
if (!targetp) return;
|
|
#if VL_DEBUG
|
|
UASSERT(!targetp->m_ownerpp, "Already linked");
|
|
#endif
|
|
targetp->m_ownerpp = &m_ptr;
|
|
}
|
|
|
|
// Make the pointer point to the target, and the target's owner pointer to this pointer
|
|
VL_ATTR_ALWINLINE void linkNonNull(Node* targetp) {
|
|
m_ptr = targetp;
|
|
#if VL_DEBUG
|
|
UASSERT(!targetp->m_ownerpp, "Already linked");
|
|
#endif
|
|
targetp->m_ownerpp = &m_ptr;
|
|
}
|
|
|
|
// Clear the pointer and return it's previous value
|
|
VL_ATTR_ALWINLINE Node* unlink() {
|
|
Node* const result = m_ptr;
|
|
#if VL_DEBUG
|
|
if (result) {
|
|
UASSERT(m_ptr->m_ownerpp == &m_ptr, "Bad back link");
|
|
// Not strictly necessary to clear this, but helps debugging
|
|
m_ptr->m_ownerpp = nullptr;
|
|
}
|
|
#endif
|
|
m_ptr = nullptr;
|
|
return result;
|
|
}
|
|
|
|
// Minimal convenience accessors and operators
|
|
VL_ATTR_ALWINLINE Node* ptr() const { return m_ptr; }
|
|
VL_ATTR_ALWINLINE operator bool() const { return m_ptr; }
|
|
VL_ATTR_ALWINLINE bool operator!() const { return !m_ptr; }
|
|
VL_ATTR_ALWINLINE Node* operator->() const { return m_ptr; }
|
|
VL_ATTR_ALWINLINE Node& operator*() const { return *m_ptr; }
|
|
};
|
|
|
|
// A single node in the pairing heap tree
|
|
struct Node VL_NOT_FINAL {
|
|
Link m_next; // Next in list of sibling heaps
|
|
Link m_kids; // Head of list of child heaps
|
|
Node** m_ownerpp = nullptr; // Pointer to the Link pointer pointing to this heap
|
|
T_Key m_key{}; // The key in the heap
|
|
|
|
// CONSTRUCTOR
|
|
explicit Node() = default;
|
|
VL_UNCOPYABLE(Node);
|
|
|
|
// METHODS
|
|
VL_ATTR_ALWINLINE const T_Key& key() const { return m_key; }
|
|
VL_ATTR_ALWINLINE bool operator<(const Node& that) const { return m_key < that.m_key; }
|
|
VL_ATTR_ALWINLINE bool operator>(const Node& that) const { return that.m_key < m_key; }
|
|
|
|
// Make newp take the place of this in the tree
|
|
VL_ATTR_ALWINLINE void replaceWith(Node* newp) {
|
|
*m_ownerpp = newp; // The owner pointer needs to point to the new node
|
|
if (newp) newp->m_ownerpp = m_ownerpp; // The new node needs to point to its owner
|
|
m_ownerpp = nullptr; // This node has no owner anymore
|
|
}
|
|
|
|
// Make newp take the place of this in the tree
|
|
VL_ATTR_ALWINLINE void replaceWithNonNull(Node* newp) {
|
|
*m_ownerpp = newp; // The owner pointer needs to point to the new node
|
|
newp->m_ownerpp = m_ownerpp; // The new node needs to point to its owner
|
|
m_ownerpp = nullptr; // This node has no owner anymore
|
|
}
|
|
|
|
// Yank this node out of the heap it currently is in. This node can then be safely inserted
|
|
// into another heap. Note that this leaves the heap the node is currently under in an
|
|
// inconsistent state, so you cannot access it anymore. Still this can save a remove if we
|
|
// don't care about the state of the source heap.
|
|
VL_ATTR_ALWINLINE void yank() {
|
|
m_next.link(nullptr);
|
|
m_kids.link(nullptr);
|
|
m_ownerpp = nullptr;
|
|
}
|
|
};
|
|
|
|
private:
|
|
// MEMBERS
|
|
|
|
// The root of the heap. Note: We do not reduce lists during insertion/removal etc, unless we
|
|
// absolutely have to. This means the root can become a list. This is ok, we will reduce
|
|
// lazily when requesting the minimum element.
|
|
mutable Link m_root;
|
|
|
|
// CONSTRUCTORS
|
|
VL_UNCOPYABLE(PairingHeap);
|
|
|
|
public:
|
|
explicit PairingHeap() = default;
|
|
|
|
// METHODS
|
|
bool empty() const { return !m_root; }
|
|
|
|
// Insert given node into this heap with given key.
|
|
void insert(Node* nodep, T_Key key) {
|
|
// Update key of node
|
|
nodep->m_key = key;
|
|
insert(nodep);
|
|
}
|
|
|
|
// Insert given node into this heap with key already set in the node
|
|
void insert(Node* nodep) {
|
|
#if VL_DEBUG
|
|
UASSERT(!nodep->m_ownerpp && !nodep->m_next && !nodep->m_kids, "Already linked");
|
|
#endif
|
|
// Just stick it at the front of the root list
|
|
nodep->m_next.link(m_root.unlink());
|
|
m_root.linkNonNull(nodep);
|
|
}
|
|
|
|
// Remove given node only from the heap it is contained in
|
|
void remove(Node* nodep) {
|
|
if (!nodep->m_next) {
|
|
// If the node does not have siblings, replace it with its children (might be empty).
|
|
nodep->replaceWith(nodep->m_kids.unlink());
|
|
} else if (!nodep->m_kids) {
|
|
// If it has siblings but no children, replace it with the siblings.
|
|
nodep->replaceWithNonNull(nodep->m_next.unlink());
|
|
} else {
|
|
// If it has both siblings and children, reduce the children and splice that
|
|
// reduced heap in place of this node
|
|
Node* const reducedKidsp = reduce(nodep->m_kids.unlink());
|
|
reducedKidsp->m_next.linkNonNull(nodep->m_next.unlink());
|
|
nodep->replaceWithNonNull(reducedKidsp);
|
|
}
|
|
}
|
|
|
|
// Returns the largest element in the heap
|
|
Node* max() const {
|
|
// Heap might be empty
|
|
if (!m_root) return nullptr;
|
|
// If the root have siblings reduce them
|
|
if (m_root->m_next) m_root.linkNonNull(reduce(m_root.unlink()));
|
|
// The root element is the largest
|
|
return m_root.ptr();
|
|
}
|
|
|
|
// Returns the second-largest element in the heap.
|
|
// This is only valid to call if 'max' returned a valid element.
|
|
Node* secondMax() const {
|
|
#if VL_DEBUG
|
|
UASSERT(m_root, "'max' would have returned nullptr");
|
|
UASSERT(!m_root->m_next, "'max' would have reduced");
|
|
#endif
|
|
// If there are no children, there is no second element
|
|
if (!m_root->m_kids) return nullptr;
|
|
// If there are multiple children, reduce them
|
|
if (m_root->m_kids->m_next) m_root->m_kids.linkNonNull(reduce(m_root->m_kids.unlink()));
|
|
// Return the now singular child, which is the second-largest element
|
|
return m_root->m_kids.ptr();
|
|
}
|
|
|
|
// Increase the key of the given node to the given new value
|
|
template <typename T_Update>
|
|
void increaseKey(Node* nodep, T_Update value) {
|
|
// Update the key
|
|
nodep->m_key.increase(value);
|
|
// Increasing the key of the root is easy
|
|
if (nodep == m_root.ptr()) return;
|
|
// Otherwise we do have a little work to do
|
|
if (!nodep->m_kids) {
|
|
// If the node has no children, replace it with its siblings (might be null)
|
|
nodep->replaceWith(nodep->m_next.unlink());
|
|
} else if (!nodep->m_next) {
|
|
// If the node has no siblings, replace it with its children
|
|
nodep->replaceWithNonNull(nodep->m_kids.unlink());
|
|
} else {
|
|
// The node has both children and siblings. Splice the first child in the place of the
|
|
// node, and extract the rest of the children with the node
|
|
Node* const kidsp = nodep->m_kids.unlink();
|
|
nodep->m_kids.link(kidsp->m_next.unlink());
|
|
kidsp->m_next.linkNonNull(nodep->m_next.unlink());
|
|
nodep->replaceWithNonNull(kidsp);
|
|
}
|
|
// Just stick the increased node at the front of the root list
|
|
nodep->m_next.linkNonNull(m_root.unlink());
|
|
m_root.linkNonNull(nodep);
|
|
}
|
|
|
|
private:
|
|
// Meld (merge) two heaps rooted at the given nodes, return the root of the new heap
|
|
VL_ATTR_ALWINLINE static Node* merge(Node* ap, Node* bp) {
|
|
#if VL_DEBUG
|
|
UASSERT(!ap->m_ownerpp && !ap->m_next, "Not root a");
|
|
UASSERT(!bp->m_ownerpp && !bp->m_next, "Not root b");
|
|
#endif
|
|
if (*ap > *bp) { // bp goes under ap
|
|
bp->m_next.link(ap->m_kids.unlink());
|
|
ap->m_kids.linkNonNull(bp);
|
|
return ap;
|
|
} else { // ap goes under bp
|
|
ap->m_next.link(bp->m_kids.unlink());
|
|
bp->m_kids.linkNonNull(ap);
|
|
return bp;
|
|
}
|
|
}
|
|
|
|
// Reduces the list of nodes starting at the given node into a single node that is returned
|
|
VL_ATTR_NOINLINE static Node* reduce(Node* nodep) {
|
|
#if VL_DEBUG
|
|
UASSERT(!nodep->m_ownerpp, "Node is linked");
|
|
#endif
|
|
// If there is only one node in the list, then there is nothing to do
|
|
if (!nodep->m_next) return nodep;
|
|
// The result node
|
|
Node* resultp = nullptr;
|
|
// Pairwise merge the child nodes
|
|
while (nodep) {
|
|
// Pop off the first nodes
|
|
Node* const ap = nodep;
|
|
// If we have an odd number of nodes, prepend the unpaired one onto the result list
|
|
if (!nodep->m_next) {
|
|
ap->m_next.link(resultp);
|
|
resultp = ap;
|
|
break;
|
|
}
|
|
// Pop off the second nodes
|
|
Node* const bp = nodep->m_next.unlink();
|
|
// Keep hold of the rest of the list
|
|
nodep = bp->m_next.unlink();
|
|
// Merge the current pair
|
|
Node* const mergedp = merge(ap, bp);
|
|
// Prepend the merged pair to the result list
|
|
mergedp->m_next.link(resultp);
|
|
resultp = mergedp;
|
|
}
|
|
// Now merge-reduce the merged pairs
|
|
while (resultp->m_next) {
|
|
// Pop first two results
|
|
Node* const ap = resultp;
|
|
Node* const bp = resultp->m_next.unlink();
|
|
// Keep hold of the rest of the list
|
|
resultp = bp->m_next.unlink();
|
|
// Merge the current pair
|
|
Node* const mergedp = merge(ap, bp);
|
|
// Prepend the merged pair to the result list
|
|
mergedp->m_next.link(resultp);
|
|
resultp = mergedp;
|
|
}
|
|
// Done
|
|
return resultp;
|
|
}
|
|
};
|
|
|
|
// The PairingHeap itself should be a simple pointer and nothing more
|
|
static_assert(sizeof(PairingHeap<int>) == sizeof(PairingHeap<int>::Node*), "Should be a pointer");
|
|
|
|
#endif // Guard
|