blob: 2232f2e997fe169fc24e4d6eb3b287217b4c1d7c [file] [log] [blame]
// Copyright 2016 The Fuchsia 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 FBL_INTRUSIVE_CONTAINER_UTILS_H_
#define FBL_INTRUSIVE_CONTAINER_UTILS_H_
#include <tuple>
#include <type_traits>
#include <utility>
#include <fbl/intrusive_pointer_traits.h>
#include <fbl/macros.h>
namespace fbl {
// DefaultKeyedObjectTraits defines a default implementation of traits used to
// manage objects stored in associative containers such as hash-tables and
// trees.
//
// At a minimum, a class or a struct which is to be used to define the
// traits of a keyed object must define the following public members.
//
// GetKey : A static method which takes a constant reference to an object (the
// type of which is infered from PtrType) and returns a KeyType
// instance corresponding to the key for an object.
// LessThan : A static method which takes two keys (key1 and key2) and returns
// true if-and-only-if key1 is considered to be less than key2 for
// sorting purposes.
// EqualTo : A static method which takes two keys (key1 and key2) and returns
// true if-and-only-if key1 is considered to be equal to key2.
//
// Rules for keys:
// ++ The type of key returned by GetKey must be compatible with the key which
// was specified for the container.
// ++ The key for an object must remain constant for as long as the object is
// contained within a container.
// ++ When comparing keys, comparisons must obey basic transative and
// commutative properties. That is to say...
// LessThan(A, B) and LessThan(B, C) implies LessThan(A, C)
// EqualTo(A, B) and EqualTo(B, C) implies EqualTo(A, C)
// EqualTo(A, B) if-and-only-if EqualTo(B, A)
// LessThan(A, B) if-and-only-if EqualTo(B, A) or (not LessThan(B, A))
//
// DefaultKeyedObjectTraits is a helper class which allows an object to be
// treated as a keyed-object by implementing a const GetKey method which returns
// a key of the appropriate type. The key type must be compatible with the
// container key type, and must have definitions of the < and == operators for
// the purpose of generating implementation of LessThan and EqualTo.
template <typename KeyType, typename ObjType>
struct DefaultKeyedObjectTraits {
static KeyType GetKey(const ObjType& obj) { return obj.GetKey(); }
static bool LessThan(const KeyType& key1, const KeyType& key2) { return key1 < key2; }
static bool EqualTo(const KeyType& key1, const KeyType& key2) { return key1 == key2; }
};
// A set of flag-style options which can be applied to container nodes in order
// to control and sanity check their behavior and compatibility at compile time.
//
// To control node options, users pass a set of options to either the
// containable mix-in class (SinglyLinkedLisable, DoublyLinkedListable, or
// WAVLTreeContainable), or directly to the node instance in the case that the
// user is specifying custom container traits.
enum class NodeOptions : uint64_t {
None = 0,
// By default, nodes will not allow either copy construction or copy
// assignment. Directly copying the contents of node storage for a structure
// which is currently in a container cannot be allowed. The result would be
// to have two objects, one of which is actually in a container, and the other
// of which is only sort-of in the container.
//
// Because of this, if code attempts to expand either the copy constructor or
// assignment operator, by default it will trigger a static assert.
//
// It may be the case, however, that a user wishes to allow copying of their
// structure while the source and destination structure are _not_ in any
// containers. In this case, pass the NodeOptions::AllowCopy flag. Nodes
// will permit copying, but will runtime ZX_DEBUG_ASSERT if either the source
// or destination exist within a container at the time of the copy. In builds
// with ZX_DEBUG_ASSERTs disabled, neither the source nor the destination's
// internal node contents will be modified. So, in the case of the following
// code:
//
// MyStructure& A = *(container.begin());
// MyStructure B = A;
// MyStructure& C = get_a_structure_reference_from_somewhere();
// C = A;
//
// ++ A will remain in the container unmodified.
// ++ B will be in no container (as it was just constructed).
// ++ C either will either remain in its container if it had been in one, or
// will remain in no container if not.
//
// Finally, it is possible that a user actually _does_ wish to copy the
// contents of a structure out of a container using either copy construction
// or using copy assignment. If this is the case, pass the
// NodeOptions::AllowCopyFromContainer flag. In addition to allowing the copy
// constructor and assignment operators, the debug asserts will be disabled in
// this situation. Users may copy the contents of their node, but not mutate
// any container membership state as part of the process.
//
AllowCopy = (1 << 0),
AllowCopyFromContainer = (1 << 1),
// See the AllowCopy and AllowCopyFromConainer flags for details. AllowMove
// and AllowMoveFromContainer are the same, simply applied to the rvalue
// constructor and assignment operators of the node.
AllowMove = (1 << 2),
AllowMoveFromContainer = (1 << 3),
// Convenience definitions
AllowCopyMove = static_cast<uint64_t>(AllowCopy) | static_cast<uint64_t>(AllowMove),
AllowCopyMoveFromContainer =
static_cast<uint64_t>(AllowCopyFromContainer) | static_cast<uint64_t>(AllowMoveFromContainer),
// Allow an object to exist in multiple containers at once, even if one or
// more of those containers tracks the object using a unique_ptr (or any other
// non-copyable pointer type).
//
// Generally, it would be a mistake to define an object which can exist in
// multiple containers concurrently, and track those objects in their
// containers using unique_ptr semantics. In theory, it should be impossible
// for two different containers to track the same object at the same time,
// each using something like a unique_ptr. This would violate the uniqueness of
// the pointer.
//
// Because of this, the ContainableBaseClasses helper (see below) will, by
// default, complain and refuse to build if someone attempts to use it in
// conjunction with a Containable mix-in which tracks objects using
// unique_ptr-style pointers if there are any other containers in the
// Containable list.
//
// There are special cases, however, where a user might want this behavior to
// be permitted. Consider an object whose lifecycle is managed by a central
// list of unique_ptrs, but which can also exist on a temporary list which
// tracks the objects using raw pointers for strictly algorithmic purposes.
// Provided that the user carefully ensures that the object does not disappear
// from the central list while it exists on the temporary list, this should be
// completely fine. More concretely, the following should be allowed provided
// that the user opts in.
//
// using MainList = fbl::TaggedDoublyLinkedList<std::unique_ptr<Obj>, MainListTag>;
// using TmpList = fbl::TaggedSinglyLinkedList<Obj*, TmpListTag>;
//
// fbl::Mutex all_objects_lock;
// MainList all_objects TA_GUARDED(all_objects_lock);
//
// void do_interesting_things() TA_EXCL(all_objects_lock) {
// fbl::AutoLock lock(&all_objects_lock);
// TmpList interesting_objects;
//
// for (auto& obj : all_objects) {
// if (object_is_interesting(obj)) {
// interesting_objects.push(obj);
// }
// }
//
// do_interesting_things_to_interesting_objects(std::move(interesting_objects));
// }
//
// Users who have carefully considered the lifecycle management of their
// objects and wish to allow this behavior should pass the
// AllowMultiContainerUptr option to their Containable mix-in.
AllowMultiContainerUptr = (1 << 4),
// Nodes with this flag permitted to be directly removed from their container,
// without needing to go through the container's erase method.
AllowRemoveFromContainer = (1 << 5),
// Enables the |clear_unsafe| operation on containers of unmanaged pointers.
AllowClearUnsafe = (1 << 6),
// Reserved bits reserved for testing purposes and should always be ignored by
// node implementations.
ReservedBits = 0xF000000000000000,
};
// Helper functions which make it a bit easier to use the enum class NodeOptions
// in a flag style fashion.
//
// The | operator will take two options and or them together to produce their
// composition without needing to do all sorts of nasty casting. In other
// words:
//
// fbl::NodeOptions::AllowX | fbl::NodeOptions::AllowY
//
// is legal.
//
// The & operator is overloaded to perform the bitwise and of the
// underlying flags and test against zero returning a bool. This allows us to
// say things like:
//
// if constexpr (SomeOptions | fbl::NodeOptions::AllowX) { ... }
//
constexpr fbl::NodeOptions operator|(fbl::NodeOptions A, fbl::NodeOptions B) {
return static_cast<fbl::NodeOptions>(
static_cast<std::underlying_type<fbl::NodeOptions>::type>(A) |
static_cast<std::underlying_type<fbl::NodeOptions>::type>(B));
}
constexpr bool operator&(fbl::NodeOptions A, fbl::NodeOptions B) {
return (static_cast<std::underlying_type<fbl::NodeOptions>::type>(A) &
static_cast<std::underlying_type<fbl::NodeOptions>::type>(B)) != 0;
}
struct DefaultObjectTag {};
// ContainableBaseClasses<> makes it easy to define types that live in multiple
// intrusive containers at once.
//
// If you didn't use this helper template, you would have to define multiple
// traits classes, each with their own node_state function, and then have
// multiple NodeState members in your class. This is noisy boilerplate, so
// instead you can just do something like the following:
//
// struct MyTag1 {};
// struct MyTag2 {};
// struct MyTag3 {};
//
// class MyClass
// : public fbl::RefCounted<MyClass>,
// public fbl::ContainableBaseClasses<
// fbl::WAVLTreeContainable<fbl::RefPtr<MyClass>, MyTag1>,
// fbl::WAVLTreeContainable<fbl::RefPtr<MyClass>, MyTag2>,
// fbl::SinglyLinkedListable<MyClass*, MyTag3>,
// [...]> { <your class definition> };
//
// Then when you create your container, you use the same tag type:
//
// fbl::TaggedWAVLTree<uint32_t, fbl::RefPtr<MyClass>, MyTag1> my_tree;
//
// The tag types themselves can be basically anything but I recommend you define
// your own empty structs to keep it simple and make it a type that you own.
//
// (Note for the curious: the tag types are necessary to solve the diamond
// problem, since your class ends up with multiple node_state_ members from the
// non-virtual multiple inheritance and the compiler needs to know which one you
// want.)
//
// When you inherit from this template, your class will also end up with a
// TagTypes member, which is just a std::tuple of your tag types, so that
// you can query these for metaprogramming purposes.
//
// You should also get relatively readable error messages for common error cases
// because of a few static_asserts; notably, you cannot:
// ++ Use any variation of unique_ptr as the PtrType here since that would defeat
// its purpose.
// ++ Explicitly use the DefaultObjectTag that is used as the tag type when
// the user does not specify one.
// ++ Pass the same tag type twice.
//
namespace internal {
template <typename... BaseClasses>
struct ContainableBaseClassEnumerator;
template <>
struct ContainableBaseClassEnumerator<> {
using ContainableTypes = std::tuple<>;
using TagTypes = std::tuple<>;
static constexpr size_t BaseClassCount = 0;
static constexpr size_t UniquePtrCount = 0;
};
template <template <typename, NodeOptions, typename> class Containable, typename PtrType,
NodeOptions Options, typename TagType, typename... Rest>
struct ContainableBaseClassEnumerator<Containable<PtrType, Options, TagType>, Rest...>
: public Containable<PtrType, Options, TagType>,
public ContainableBaseClassEnumerator<Rest...> {
static_assert(!std::is_same_v<TagType, DefaultObjectTag>,
"Do not use fbl::DefaultObjectTag when inheriting from "
"fbl::ContainableBaseClasses. Define your own instead.");
static_assert((!std::is_same_v<TagType, typename Rest::TagType> && ...),
"All tag types used with fbl::ContainableBaseClassEnumerator must be unique.");
using ContainableTypes = decltype(std::tuple_cat(
std::declval<std::tuple<Containable<PtrType, Options, TagType>>>(),
std::declval<typename ContainableBaseClassEnumerator<Rest...>::ContainableTypes>()));
using TagTypes = decltype(
std::tuple_cat(std::declval<std::tuple<TagType>>(),
std::declval<typename ContainableBaseClassEnumerator<Rest...>::TagTypes>()));
static constexpr size_t BaseClassCount =
1 + ContainableBaseClassEnumerator<Rest...>::BaseClassCount;
static constexpr size_t UniquePtrCount = !internal::ContainerPtrTraits<PtrType>::CanCopy +
ContainableBaseClassEnumerator<Rest...>::UniquePtrCount;
static_assert((UniquePtrCount == 0) || ((UniquePtrCount == 1) && (BaseClassCount == 1)) ||
(Options & NodeOptions::AllowMultiContainerUptr),
"Containers of pointers with unique pointer semantics cannot be combined with any "
"other containers when using ContainableBaseClasses unless you specify the "
"AllowMultiContainerUptr flag in your node options.");
};
} // namespace internal
template <typename... BaseClasses>
struct ContainableBaseClasses {
using Enumerator = internal::ContainableBaseClassEnumerator<BaseClasses...>;
using ContainableTypes = typename Enumerator::ContainableTypes;
using TagTypes = typename Enumerator::TagTypes;
template <typename Tag, size_t N = 0>
static constexpr size_t TagIndex() {
static_assert(N < std::tuple_size<ContainableTypes>(), "Tag not found!");
using ContainableType = typename std::tuple_element<N, ContainableTypes>::type;
if constexpr (std::is_same_v<Tag, typename ContainableType::TagType>) {
return N;
} else {
return TagIndex<Tag, N + 1>();
}
}
template <typename Tag>
auto& GetContainableByTag() {
constexpr size_t Index = TagIndex<Tag>();
return std::get<Index>(contained_nodes_);
}
template <typename Tag>
const auto& GetContainableByTag() const {
constexpr size_t Index = TagIndex<Tag>();
return std::get<Index>(contained_nodes_);
}
ContainableTypes contained_nodes_;
};
namespace internal {
DECLARE_HAS_MEMBER_TYPE(has_tag_types, TagTypes);
}
// These are free function because making it a member function presents
// complicated lookup issues since the specific Containable classes exist as
// members of the ContainableBaseClasses<...>, and you'd need to say
// obj.template GetContainableByTag<TagType>().InContainer (or
// RemoveFromContainer), which is super ugly.
template <typename TagType = DefaultObjectTag, typename Containable>
bool InContainer(const Containable& c) {
if constexpr (std::is_same_v<TagType, DefaultObjectTag>) {
return c.InContainer();
} else {
return c.template GetContainableByTag<TagType>().InContainer();
}
}
template <typename TagType = DefaultObjectTag, typename Containable>
auto RemoveFromContainer(Containable& c) {
if constexpr (std::is_same_v<TagType, DefaultObjectTag>) {
return c.RemoveFromContainer();
} else {
return c.template GetContainableByTag<TagType>().RemoveFromContainer();
}
}
// An enumeration which can be used as a template argument on list types to
// control the order of operation needed to compute the size of the list. When
// set to SizeOrder::N, the list's size will not be maintained and there will be
// no valid size() method to call. The only way to fetch the size of a list
// would be via |size_slow()|. Alternatively, a user may specify
// SizeOrder::Constant. In this case, the storage size of the list itself will
// grow by a size_t, and the size of the list will be maintained as elements are
// added and removed.
enum class SizeOrder { N, Constant };
} // namespace fbl
namespace fbl::internal {
// DirectEraseUtils
//
// A utility class used by HashTable to implement an O(n) or O(k) direct erase
// operation depending on whether or not the HashTable's bucket type supports
// O(k) erase.
template <typename ContainerType, typename Enable = void>
struct DirectEraseUtils;
template <typename ContainerType>
struct DirectEraseUtils<
ContainerType, std::enable_if_t<ContainerType::SupportsConstantOrderErase == false, void>> {
using PtrTraits = typename ContainerType::PtrTraits;
using PtrType = typename PtrTraits::PtrType;
using ValueType = typename PtrTraits::ValueType;
static PtrType erase(ContainerType& container, ValueType& obj) {
return container.erase_if([&obj](const ValueType& other) -> bool { return &obj == &other; });
}
};
template <typename ContainerType>
struct DirectEraseUtils<ContainerType,
std::enable_if_t<ContainerType::SupportsConstantOrderErase == true, void>> {
using PtrTraits = typename ContainerType::PtrTraits;
using PtrType = typename PtrTraits::PtrType;
using ValueType = typename PtrTraits::ValueType;
static PtrType erase(ContainerType& container, ValueType& obj) { return container.erase(obj); }
};
// KeyEraseUtils
//
// A utility class used by HashTable to implement an O(n) or O(k) erase-by-key
// operation depending on whether or not the HashTable's bucket type is
// associative or not.
template <typename ContainerType, typename KeyTraits, typename Enable = void>
struct KeyEraseUtils;
template <typename ContainerType, typename KeyTraits>
struct KeyEraseUtils<ContainerType, KeyTraits,
std::enable_if_t<ContainerType::IsAssociative == false, void>> {
using PtrTraits = typename ContainerType::PtrTraits;
using PtrType = typename PtrTraits::PtrType;
using ValueType = typename PtrTraits::ValueType;
template <typename KeyType>
static PtrType erase(ContainerType& container, const KeyType& key) {
return container.erase_if([key](const ValueType& other) -> bool {
return KeyTraits::EqualTo(key, KeyTraits::GetKey(other));
});
}
};
template <typename ContainerType, typename KeyTraits>
struct KeyEraseUtils<ContainerType, KeyTraits,
std::enable_if_t<ContainerType::IsAssociative == true, void>> {
using PtrTraits = typename ContainerType::PtrTraits;
using PtrType = typename PtrTraits::PtrType;
template <typename KeyType>
static PtrType erase(ContainerType& container, const KeyType& key) {
return container.erase(key);
}
};
// Swaps two plain old data types with size no greater than 64 bits.
template <typename T, typename = std::enable_if_t<std::is_pod_v<T> && (sizeof(T) <= 8)>>
inline void Swap(T& a, T& b) noexcept {
T tmp = a;
a = b;
b = tmp;
}
// Notes on container sentinels:
//
// Intrusive container implementations employ a slightly tricky pattern where
// sentinel values are used in place of nullptr in various places in the
// internal data structure in order to make some operations a bit easier.
// Generally speaking, a sentinel pointer is a pointer to a container with the
// sentinel bit set. It is cast and stored in the container's data structure as
// a pointer to an element which the container contains, even though it is
// actually a slightly damaged pointer to the container itself.
//
// An example of where this is used is in the doubly linked list implementation.
// The final element in the list holds the container's sentinel value instead of
// nullptr or a pointer to the head of the list. When an iterator hits the end
// of the list, it knows that it is at the end (because the sentinel bit is set)
// but can still get back to the list itself (by clearing the sentinel bit in
// the pointer) without needing to store an explicit pointer to the list itself.
//
// Care must be taken when using sentinel values. They are *not* valid pointers
// and must never be dereferenced, recovered into an managed representation, or
// returned to a user. In addition, it is essential that a legitimate pointer
// to a container never need to set the sentinel bit. Currently, bit 0 is being
// used because it should never be possible to have a proper container instance
// which is odd-aligned.
constexpr uintptr_t kContainerSentinelBit = 1U;
// Create a sentinel pointer from a raw pointer, converting it to the specified
// type in the process.
template <typename T, typename U, typename = std::enable_if_t<std::is_pointer_v<T>>>
constexpr T make_sentinel(U* ptr) {
return reinterpret_cast<T>(reinterpret_cast<uintptr_t>(ptr) | kContainerSentinelBit);
}
template <typename T, typename = std::enable_if_t<std::is_pointer_v<T>>>
constexpr T make_sentinel(decltype(nullptr)) {
return reinterpret_cast<T>(kContainerSentinelBit);
}
// Turn a sentinel pointer back into a normal pointer, automatically
// re-interpreting its type in the process.
template <typename T, typename U, typename = std::enable_if_t<std::is_pointer_v<T>>>
constexpr T unmake_sentinel(U* sentinel) {
return reinterpret_cast<T>(reinterpret_cast<uintptr_t>(sentinel) & ~kContainerSentinelBit);
}
// Test to see if a pointer is a sentinel pointer.
template <typename T>
constexpr bool is_sentinel_ptr(const T* ptr) {
return (reinterpret_cast<uintptr_t>(ptr) & kContainerSentinelBit) != 0;
}
// Test to see if a pointer (which may be a sentinel) is valid. Valid in this
// context means that the pointer is not null, and is not a sentinel.
template <typename T>
constexpr bool valid_sentinel_ptr(const T* ptr) {
return ptr && !is_sentinel_ptr(ptr);
}
DECLARE_HAS_MEMBER_FN(has_node_state, node_state);
// Helpers which can be used to determine the NodeState type and
// NodeState::PtrType types returned by the node_state method of a TraitClass
// |RefType|. These are used primarily in tests and in static_asserts in the
// code as sanity checks.
template <typename TraitClass, typename RefType>
using node_state_t = std::decay_t<std::invoke_result_t<decltype(TraitClass::node_state), RefType>>;
template <typename TraitClass, typename RefType>
using node_ptr_t = typename node_state_t<TraitClass, RefType>::PtrType;
// SizeTracker is a partially specialized internal class used to track (or
// explicitly to not track) the size of Lists in the fbl:: containers. Its
// behavior and size depends on the SizeOrder template parameter passed to it.
//
// Please note that to use this class, containers must (sadly) derive from it, they
// cannot simply encapsulate it. The SizeOrder::N version of the tracker is
// nominally of 0 size, however 0 sized members of a struct/class are not allowed
// in C++. Attempting to put a 0 sized member into a class results in at least
// 1 byte of size impact, which changes the size of the entire object.
//
// 0 sized base classes, however, are totally fine. So, if we encapsulate a
// SizeTracker<SizeOrder::N>, then our container gets bigger for no reason, but
// if we derive from one, then our container stays the size that we expect it
// to.
//
// static_assert tests for this exist in the non-sized doubly and singly linked
// list tests.
template <SizeOrder>
class SizeTracker;
template <>
class SizeTracker<SizeOrder::N> {
protected:
constexpr SizeTracker() = default;
~SizeTracker() = default;
// No copy, no move.
SizeTracker(const SizeTracker&) = delete;
SizeTracker& operator=(const SizeTracker&) = delete;
SizeTracker(SizeTracker&& other) = delete;
SizeTracker& operator=(SizeTracker&& other) = delete;
// Inc, Dec, Reset, and swap operations are no-ops. There is no count
// accessor. Anyone who attempts to access count has made a mistake.
void IncSizeTracker(size_t) {}
void DecSizeTracker(size_t) {}
void ResetSizeTracker() {}
void SwapSizeTracker(SizeTracker&) {}
};
template <>
class SizeTracker<SizeOrder::Constant> {
protected:
constexpr SizeTracker() = default;
~SizeTracker() = default;
// No copy, no move.
SizeTracker(const SizeTracker&) = delete;
SizeTracker& operator=(const SizeTracker&) = delete;
SizeTracker(SizeTracker&& other) = delete;
SizeTracker& operator=(SizeTracker&& other) = delete;
// Basic operations for manipulating the count storage.
void IncSizeTracker(size_t amt) { size_tracker_count_ += amt; }
void DecSizeTracker(size_t amt) { size_tracker_count_ -= amt; }
void ResetSizeTracker() { size_tracker_count_ = 0; }
void SwapSizeTracker(SizeTracker& other) {
std::swap(size_tracker_count_, other.size_tracker_count_);
}
size_t SizeTrackerCount() const { return size_tracker_count_; }
private:
size_t size_tracker_count_ = 0;
};
} // namespace fbl::internal
#endif // FBL_INTRUSIVE_CONTAINER_UTILS_H_