blob: 85556fe44cff1951ffbebd7978592606bc62adf4 [file] [log] [blame]
// Copyright 2023 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_BASE_DOUBLY_THREADED_LIST_H_
#define V8_BASE_DOUBLY_THREADED_LIST_H_
#include "src/base/compiler-specific.h"
#include "src/base/iterator.h"
#include "src/base/logging.h"
namespace v8::base {
template <typename T>
struct DoublyThreadedListTraits {
static T** prev(T t) { return t->prev(); }
static T* next(T t) { return t->next(); }
static bool non_empty(T t) { return t != nullptr; }
};
// `DoublyThreadedList` is an intrusive doubly-linked list that threads through
// its nodes, somewhat like `v8::base::ThreadedList`.
//
// Of interest is the fact that instead of having regular next/prev pointers,
// nodes have a regular "next" pointer, but their "prev" pointer contains the
// address of the "next" of the previous element. This way, removing an element
// doesn't require special treatment for the head of the list, and does not
// even require to know the head of the list.
template <class T, class DTLTraits = DoublyThreadedListTraits<T>>
class DoublyThreadedList {
public:
// Since C++17, it is possible to have a sentinel end-iterator that is not an
// iterator itself.
class end_iterator {};
class iterator : public base::iterator<std::forward_iterator_tag, T> {
public:
explicit iterator(T head) : curr_(head) {}
T operator*() { return curr_; }
iterator& operator++() {
DCHECK(DTLTraits::non_empty(curr_));
curr_ = *DTLTraits::next(curr_);
return *this;
}
iterator operator++(int) {
DCHECK(DTLTraits::non_empty(curr_));
iterator tmp(*this);
operator++();
return tmp;
}
bool operator==(end_iterator) { return !DTLTraits::non_empty(curr_); }
bool operator!=(end_iterator) { return DTLTraits::non_empty(curr_); }
private:
friend DoublyThreadedList;
T curr_;
};
// Removes `x` from the list. Iterators that are currently on `x` are
// invalidated. To remove while iterating, use RemoveAt.
static void Remove(T x) {
if (*DTLTraits::prev(x) == nullptr) {
DCHECK(empty(*DTLTraits::next(x)));
// {x} already removed from the list.
return;
}
T** prev = DTLTraits::prev(x);
T* next = DTLTraits::next(x);
**prev = *next;
if (DTLTraits::non_empty(*next)) *DTLTraits::prev(*next) = *prev;
*DTLTraits::prev(x) = nullptr;
*DTLTraits::next(x) = {};
}
DoublyThreadedList() = default;
// Defining move constructor so that when resizing container, the prev pointer
// of the next(head_) doesn't point to the old head_ but rather to the new
// one.
DoublyThreadedList(DoublyThreadedList&& other) V8_NOEXCEPT {
head_ = other.head_;
if (DTLTraits::non_empty(head_)) {
*DTLTraits::prev(head_) = &head_;
}
other.head_ = {};
}
// Add `x` at the beginning of the list. `x` will not be visible to any
// existing iterator. Does not invalidate any existing iterator.
void PushFront(T x) {
DCHECK(empty(*DTLTraits::next(x)));
DCHECK_EQ(*DTLTraits::prev(x), nullptr);
*DTLTraits::next(x) = head_;
*DTLTraits::prev(x) = &head_;
if (DTLTraits::non_empty(head_)) {
*DTLTraits::prev(head_) = DTLTraits::next(x);
}
head_ = x;
}
T Front() const {
DCHECK(!empty());
return *begin();
}
void PopFront() {
DCHECK(!empty());
Remove(Front());
}
bool empty() const { return !DTLTraits::non_empty(head_); }
iterator begin() const { return iterator{head_}; }
end_iterator end() const { return end_iterator{}; }
// Removes the element at `it`, and make `it` point to the next element.
// Iterators on the same element as `it` are invalidated. Other iterators are
// not affected.
iterator RemoveAt(iterator& it) {
DCHECK(DTLTraits::non_empty(it.curr_));
T curr = *it;
T next = *DTLTraits::next(curr);
Remove(curr);
return iterator{next};
}
bool ContainsSlow(T needle) const {
for (T element : *this) {
if (element == needle) {
return true;
}
}
return false;
}
private:
static bool empty(T x) { return !DTLTraits::non_empty(x); }
T head_{};
};
} // namespace v8::base
#endif // V8_BASE_DOUBLY_THREADED_LIST_H_