verilator/src/V3List.h

344 lines
14 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Doubly linked endogenous (intrusive) list
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2024 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_V3LIST_H_
#define VERILATOR_V3LIST_H_
#include "config_build.h"
#include "verilatedos.h"
#include "V3Error.h"
#include <type_traits>
#include <utility>
//============================================================================
// The list links (just 2 pointers), to be instantiated as a member in the
// list element base class 'T_Base'. They are considered mutable, even if the
// list element is 'const', as they are only used for storing the elements in
// a V3List. That is, you can store const elements in a V3List.
template <typename T_Base>
class V3ListLinks final {
// The V3List itself, but nothing else can access the link pointers
template <typename B, V3ListLinks<B>& (B::*)(), typename>
friend class V3List;
T_Base* m_nextp = nullptr; // Next element in list
T_Base* m_prevp = nullptr; // Previous element in list
public:
V3ListLinks() = default;
~V3ListLinks() {
#ifdef VL_DEBUG
m_nextp = reinterpret_cast<T_Base*>(1);
m_prevp = reinterpret_cast<T_Base*>(1);
#endif
}
VL_UNCOPYABLE(V3ListLinks);
VL_UNMOVABLE(V3ListLinks);
};
//============================================================================
// Generic endogenous (or intrusive) doubly linked list, with links stored
// inside the elements. The template parameters are:
// - 'T_Base' is the base type of elements that contains the V3ListLinks
// instance as a data member.
// - 'LinksGetter' is a member function pointer that returns a reference to
// the links within 'T_Base'. Note that you should be able to use a data
// member pointer, instead of a function pointer, but that is buggy in MSVC.
// - 'T_Element' is the actual type of elements, which must be the same,
// or a subtype of 'T_Base'.
template <typename T_Base, //
V3ListLinks<T_Base>& (T_Base::*LinksGetter)(), //
typename T_Element = T_Base>
class V3List final {
static_assert(std::is_base_of<T_Base, T_Element>::value,
"'T_Element' must be a subtype of 'T_Base");
using ListType = V3List<T_Base, LinksGetter, T_Element>;
// MEMBERS
T_Base* m_headp = nullptr;
T_Base* m_lastp = nullptr;
// Given the T_Base, return the Links. The links are always mutable, even in const elements.
VL_ATTR_ALWINLINE
static V3ListLinks<T_Base>& toLinks(const T_Base* elementp) {
return (const_cast<T_Base*>(elementp)->*LinksGetter)();
}
VL_ATTR_ALWINLINE
static void prefetch(const T_Base* elementp, const T_Base* fallbackp) {
UDEBUGONLY(UASSERT(fallbackp, "Prefetch fallback pointer must be non nullptr"););
// This compiles to a branchless prefetch with cmove, with the address always valid
VL_PREFETCH_RW(elementp ? elementp : fallbackp);
}
// Iterator class template for V3List. This is just enough to support range based for loops
// and basic usage. Feel free to extend as required.
template <typename T_IteratorElement, bool T_Reverse>
class SimpleItertatorImpl final {
static_assert(std::is_same<T_IteratorElement, T_Element>::value
|| std::is_same<T_IteratorElement, const T_Element>::value,
"'SimpleItertatorImpl' must be used with element type only");
// The List itself, but nothing else can construct iterators
template <typename B, V3ListLinks<B>& (B::*)(), typename>
friend class V3List;
using IteratorType = SimpleItertatorImpl<T_IteratorElement, T_Reverse>;
T_Base* m_currp; // Currently iterated element, or 'nullptr' for 'end()' iterator
VL_ATTR_ALWINLINE
explicit SimpleItertatorImpl(T_Base* elementp)
: m_currp{elementp} {}
VL_ATTR_ALWINLINE
static T_Base* step(T_Base* currp) {
if VL_CONSTEXPR_CXX17 (T_Reverse) {
return toLinks(currp).m_prevp;
} else {
return toLinks(currp).m_nextp;
}
}
public:
// Dereference
VL_ATTR_ALWINLINE
T_IteratorElement& operator*() const {
UDEBUGONLY(UASSERT(m_currp, "Dereferencing end of list iterator"););
prefetch(step(m_currp), m_currp);
return *static_cast<T_IteratorElement*>(m_currp);
}
// Pre increment
VL_ATTR_ALWINLINE
IteratorType& operator++() {
UDEBUGONLY(UASSERT(m_currp, "Pre-incrementing end of list iterator"););
m_currp = step(m_currp);
return *this;
}
// Post increment
VL_ATTR_ALWINLINE
IteratorType operator++(int) {
UDEBUGONLY(UASSERT(m_currp, "Post-incrementing end of list iterator"););
T_Base* const elementp = m_currp;
m_currp = step(m_currp);
return IteratorType{elementp};
}
VL_ATTR_ALWINLINE
bool operator==(const IteratorType& other) const { return m_currp == other.m_currp; }
VL_ATTR_ALWINLINE
bool operator!=(const IteratorType& other) const { return m_currp != other.m_currp; }
// Convert to const iterator
VL_ATTR_ALWINLINE
operator SimpleItertatorImpl<const T_IteratorElement, T_Reverse>() const {
return SimpleItertatorImpl<const T_IteratorElement, T_Reverse>{m_currp};
}
};
// Proxy class for creating unlinkable iterators, so we can use
// 'for (T_Element* const ptr : list.unlinkable()) list.unlink(ptr);'
class UnlinkableProxy final {
// The List itself, but nothing else can construct UnlinkableProxy
template <typename B, V3ListLinks<B>& (B::*)(), typename>
friend class V3List;
ListType& m_list; // The proxied list
explicit UnlinkableProxy(ListType& list)
: m_list{list} {}
// Unlinkable iterator class template. This only supports enough for range based for loops.
// If you want something fancier, use and manage the direct iterator manually.
template <typename T_IteratorElement>
class UnlinkableItertatorImpl final {
static_assert(std::is_same<T_IteratorElement, T_Element>::value
|| std::is_same<T_IteratorElement, const T_Element>::value,
"'UnlinkableItertatorImpl' must be used with element type only");
// The UnlinkableProxy, but nothing else can construct unlinkable iterators
friend class UnlinkableProxy;
using IteratorType = UnlinkableItertatorImpl<T_IteratorElement>;
T_Base* m_currp; // Currently iterated element, or 'nullptr' for 'end()' iterator
T_Base* m_nextp; // Next element after current, or 'nullptr' for 'end()' iterator
VL_ATTR_ALWINLINE
explicit UnlinkableItertatorImpl(T_Base* elementp)
: m_currp{elementp}
, m_nextp{toLinks(m_currp).m_nextp} {}
VL_ATTR_ALWINLINE
explicit UnlinkableItertatorImpl(std::nullptr_t)
: m_currp{nullptr}
, m_nextp{nullptr} {}
public:
// Dereference - Note this returns a pointer.
VL_ATTR_ALWINLINE
T_IteratorElement* operator*() const {
UDEBUGONLY(UASSERT(m_currp, "Dereferencing end of list iterator"););
prefetch(m_nextp, m_currp);
return static_cast<T_IteratorElement*>(m_currp);
}
// Pre increment - Keeps hold of current next pointer.
VL_ATTR_ALWINLINE
IteratorType& operator++() {
UDEBUGONLY(UASSERT(m_currp, "Pre-incrementing end of list iterator"););
m_currp = m_nextp;
m_nextp = m_currp ? toLinks(m_currp).m_nextp : nullptr;
return *this;
}
VL_ATTR_ALWINLINE
bool operator!=(const IteratorType& other) const { return m_currp != other.m_currp; }
};
public:
using iterator = UnlinkableItertatorImpl<T_Element>;
using const_iterator = UnlinkableItertatorImpl<const T_Element>;
iterator begin() { //
return m_list.m_headp ? iterator{m_list.m_headp} : end();
}
const_iterator begin() const {
return m_list.m_headp ? const_iterator{m_list.m_headp} : end();
}
iterator end() { return iterator{nullptr}; }
const_iterator end() const { return const_iterator{nullptr}; }
};
public:
using iterator = SimpleItertatorImpl<T_Element, /* T_Reverse: */ false>;
using const_iterator = SimpleItertatorImpl<const T_Element, /* T_Reverse: */ false>;
using reverse_iterator = SimpleItertatorImpl<T_Element, /* T_Reverse: */ true>;
using const_reverse_iterator = SimpleItertatorImpl<const T_Element, /* T_Reverse: */ true>;
// CONSTRUCTOR
V3List() = default;
~V3List() {
#ifdef VL_DEBUG
m_headp = reinterpret_cast<T_Element*>(1);
m_lastp = reinterpret_cast<T_Element*>(1);
#endif
}
VL_UNCOPYABLE(V3List);
VL_UNMOVABLE(V3List);
// METHDOS
bool empty() const { return !m_headp; }
bool hasSingleElement() const { return m_headp && m_headp == m_lastp; }
bool hasMultipleElements() const { return m_headp && m_headp != m_lastp; }
// These return pointers, as we often want to unlink/delete them, and can also signal empty
T_Element* frontp() { return static_cast<T_Element*>(m_headp); }
const T_Element* frontp() const { return static_cast<T_Element*>(m_headp); }
T_Element* backp() { return static_cast<T_Element*>(m_lastp); }
const T_Element* backp() const { return static_cast<T_Element*>(m_lastp); }
// Standard iterators. The iterator is only invalidated if the element it points to is
// unlinked. Other list operations do not invalidate the itartor. If you want to be able to
// unlink the currently iterated element, use 'unlinkable()' below.
iterator begin() { return iterator{m_headp}; }
const_iterator begin() const { return const_iterator{m_headp}; }
iterator end() { return iterator{nullptr}; }
const_iterator end() const { return const_iterator{nullptr}; }
reverse_iterator rbegin() { return reverse_iterator{m_lastp}; }
const_reverse_iterator rbegin() const { return const_reverse_iterator{m_lastp}; }
reverse_iterator rend() { return reverse_iterator{nullptr}; }
const_reverse_iterator rend() const { return const_reverse_iterator{nullptr}; }
// Handle to create unlinkable iterators, which allows unlinking the currently iterated
// element without invalidating the iterator. However, every other operation that mutates
// the list invalidates the unlinkable iterator!
UnlinkableProxy unlinkable() { return UnlinkableProxy{*this}; }
// Link (insert) existing element at front
void linkFront(const T_Element* elementp) {
auto& links = toLinks(elementp);
links.m_nextp = m_headp;
links.m_prevp = nullptr;
if (m_headp) toLinks(m_headp).m_prevp = const_cast<T_Element*>(elementp);
m_headp = const_cast<T_Element*>(elementp);
if (!m_lastp) m_lastp = m_headp;
}
// Link (insert) existing element at back
void linkBack(const T_Element* elementp) {
auto& links = toLinks(elementp);
links.m_nextp = nullptr;
links.m_prevp = m_lastp;
if (m_lastp) toLinks(m_lastp).m_nextp = const_cast<T_Element*>(elementp);
m_lastp = const_cast<T_Element*>(elementp);
if (!m_headp) m_headp = m_lastp;
}
// Unlink (remove) and return element at the front. Returns 'nullptr' if the list is empty.
T_Element* unlinkFront() {
T_Element* const headp = m_headp;
if (headp) unlink(headp);
return headp;
}
// Unlink (remove) and return element at the back. Returns 'nullptr' if the list is empty.
T_Element* unlinkBack() {
T_Element* const lastp = m_lastp;
if (lastp) unlink(lastp);
return lastp;
}
// Unlink (remove) the given element.
void unlink(const T_Element* elementp) {
auto& links = toLinks(elementp);
if (links.m_nextp) toLinks(links.m_nextp).m_prevp = links.m_prevp;
if (links.m_prevp) toLinks(links.m_prevp).m_nextp = links.m_nextp;
if (m_headp == elementp) m_headp = links.m_nextp;
if (m_lastp == elementp) m_lastp = links.m_prevp;
links.m_prevp = nullptr;
links.m_nextp = nullptr;
}
// Swap elements of 2 lists
void swap(ListType& other) {
std::swap(m_headp, other.m_headp);
std::swap(m_lastp, other.m_lastp);
}
// Take elements from 'other' and link (insert) them all before the given position.
void splice(const_iterator pos, ListType& other) {
if (empty()) {
swap(other);
} else if (other.empty()) {
return;
} else {
UASSERT(pos == end(), "Sorry, only splicing at the end is implemented at the moment");
toLinks(m_lastp).m_nextp = other.m_headp;
toLinks(other.m_headp).m_prevp = m_lastp;
m_lastp = other.m_lastp;
other.m_headp = nullptr;
other.m_lastp = nullptr;
}
}
// This is O(n)!
size_t size() const {
size_t result = 0;
for (auto it = begin(); it != end(); ++it) ++result;
return result;
}
};
#endif // Guard