From ed80d6ec8c10dfd346030a309e161d3044bc74cc Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Wed, 21 Apr 2021 05:06:11 -0700 Subject: [PATCH] util: add compile-time validation tests for intrusive red black trees --- .../include/vapours/freebsd/tree.hpp | 104 ++++---- .../util/util_intrusive_red_black_tree.hpp | 2 +- .../include/vapours/util/util_scope_guard.hpp | 6 +- .../test/test_intrusive_red_black_tree.cpp | 232 ++++++++++++++++++ 4 files changed, 288 insertions(+), 56 deletions(-) create mode 100644 libraries/libvapours/source/test/test_intrusive_red_black_tree.cpp diff --git a/libraries/libvapours/include/vapours/freebsd/tree.hpp b/libraries/libvapours/include/vapours/freebsd/tree.hpp index c174c54a0..3e4c41087 100644 --- a/libraries/libvapours/include/vapours/freebsd/tree.hpp +++ b/libraries/libvapours/include/vapours/freebsd/tree.hpp @@ -64,23 +64,23 @@ namespace ams::freebsd { [[nodiscard]] constexpr ALWAYS_INLINE T *Left() { return this->rbe_left; } [[nodiscard]] constexpr ALWAYS_INLINE const T *Left() const { return this->rbe_left; } - ALWAYS_INLINE void SetLeft(T *e) { this->rbe_left = e; } + constexpr ALWAYS_INLINE void SetLeft(T *e) { this->rbe_left = e; } [[nodiscard]] constexpr ALWAYS_INLINE T *Right() { return this->rbe_right; } [[nodiscard]] constexpr ALWAYS_INLINE const T *Right() const { return this->rbe_right; } - ALWAYS_INLINE void SetRight(T *e) { this->rbe_right = e; } + constexpr ALWAYS_INLINE void SetRight(T *e) { this->rbe_right = e; } [[nodiscard]] constexpr ALWAYS_INLINE T *Parent() { return this->rbe_parent; } [[nodiscard]] constexpr ALWAYS_INLINE const T *Parent() const { return this->rbe_parent; } - ALWAYS_INLINE void SetParent(T *e) { this->rbe_parent = e; } + constexpr ALWAYS_INLINE void SetParent(T *e) { this->rbe_parent = e; } [[nodiscard]] constexpr ALWAYS_INLINE bool IsBlack() const { return this->rbe_color == RBColor::RB_BLACK; } [[nodiscard]] constexpr ALWAYS_INLINE bool IsRed() const { return this->rbe_color == RBColor::RB_RED; } [[nodiscard]] constexpr ALWAYS_INLINE RBColor Color() const { return this->rbe_color; } - ALWAYS_INLINE void SetColor(RBColor c) { this->rbe_color = c; } + constexpr ALWAYS_INLINE void SetColor(RBColor c) { this->rbe_color = c; } }; template struct CheckRBEntry { static constexpr bool value = false; }; @@ -102,7 +102,7 @@ namespace ams::freebsd { public: [[nodiscard]] constexpr ALWAYS_INLINE T *Root() { return this->rbh_root; } [[nodiscard]] constexpr ALWAYS_INLINE const T *Root() const { return this->rbh_root; } - ALWAYS_INLINE void SetRoot(T *root) { this->rbh_root = root; } + constexpr ALWAYS_INLINE void SetRoot(T *root) { this->rbh_root = root; } [[nodiscard]] constexpr ALWAYS_INLINE bool IsEmpty() const { return this->Root() == nullptr; } }; @@ -187,53 +187,6 @@ namespace ams::freebsd { RB_SET_PARENT(elm, tmp); } - template requires HasRBEntry - constexpr void RB_INSERT_COLOR(RBHead &head, T *elm) { - T *parent = nullptr, *tmp = nullptr; - while ((parent = RB_PARENT(elm)) != nullptr && RB_IS_RED(parent)) { - T *gparent = RB_PARENT(parent); - if (parent == RB_LEFT(gparent)) { - tmp = RB_RIGHT(gparent); - if (tmp && RB_IS_RED(tmp)) { - RB_SET_COLOR(tmp, RBColor::RB_BLACK); - RB_SET_BLACKRED(parent, gparent); - elm = gparent; - continue; - } - - if (RB_RIGHT(parent) == elm) { - RB_ROTATE_LEFT(head, parent, tmp); - tmp = parent; - parent = elm; - elm = tmp; - } - - RB_SET_BLACKRED(parent, gparent); - RB_ROTATE_RIGHT(head, gparent, tmp); - } else { - tmp = RB_LEFT(gparent); - if (tmp && RB_IS_RED(tmp)) { - RB_SET_COLOR(tmp, RBColor::RB_BLACK); - RB_SET_BLACKRED(parent, gparent); - elm = gparent; - continue; - } - - if (RB_LEFT(parent) == elm) { - RB_ROTATE_RIGHT(head, parent, tmp); - tmp = parent; - parent = elm; - elm = tmp; - } - - RB_SET_BLACKRED(parent, gparent); - RB_ROTATE_LEFT(head, gparent, tmp); - } - } - - RB_SET_COLOR(head.Root(), RBColor::RB_BLACK); - } - template requires HasRBEntry constexpr void RB_REMOVE_COLOR(RBHead &head, T *parent, T *elm) { T *tmp; @@ -409,6 +362,53 @@ namespace ams::freebsd { return old; } + template requires HasRBEntry + constexpr void RB_INSERT_COLOR(RBHead &head, T *elm) { + T *parent = nullptr, *tmp = nullptr; + while ((parent = RB_PARENT(elm)) != nullptr && RB_IS_RED(parent)) { + T *gparent = RB_PARENT(parent); + if (parent == RB_LEFT(gparent)) { + tmp = RB_RIGHT(gparent); + if (tmp && RB_IS_RED(tmp)) { + RB_SET_COLOR(tmp, RBColor::RB_BLACK); + RB_SET_BLACKRED(parent, gparent); + elm = gparent; + continue; + } + + if (RB_RIGHT(parent) == elm) { + RB_ROTATE_LEFT(head, parent, tmp); + tmp = parent; + parent = elm; + elm = tmp; + } + + RB_SET_BLACKRED(parent, gparent); + RB_ROTATE_RIGHT(head, gparent, tmp); + } else { + tmp = RB_LEFT(gparent); + if (tmp && RB_IS_RED(tmp)) { + RB_SET_COLOR(tmp, RBColor::RB_BLACK); + RB_SET_BLACKRED(parent, gparent); + elm = gparent; + continue; + } + + if (RB_LEFT(parent) == elm) { + RB_ROTATE_RIGHT(head, parent, tmp); + tmp = parent; + parent = elm; + elm = tmp; + } + + RB_SET_BLACKRED(parent, gparent); + RB_ROTATE_LEFT(head, gparent, tmp); + } + } + + RB_SET_COLOR(head.Root(), RBColor::RB_BLACK); + } + template requires HasRBEntry constexpr ALWAYS_INLINE T *RB_INSERT(RBHead &head, T *elm, Compare cmp) { T *parent = nullptr; diff --git a/libraries/libvapours/include/vapours/util/util_intrusive_red_black_tree.hpp b/libraries/libvapours/include/vapours/util/util_intrusive_red_black_tree.hpp index c44cab756..ca5ec4f90 100644 --- a/libraries/libvapours/include/vapours/util/util_intrusive_red_black_tree.hpp +++ b/libraries/libvapours/include/vapours/util/util_intrusive_red_black_tree.hpp @@ -550,7 +550,7 @@ namespace ams::util { constexpr ALWAYS_INLINE const Derived *GetNext() const { return static_cast(impl::IntrusiveRedBlackTreeImpl::GetNext(this)); } }; - template + template requires std::derived_from class IntrusiveRedBlackTreeBaseTraits { public: template diff --git a/libraries/libvapours/include/vapours/util/util_scope_guard.hpp b/libraries/libvapours/include/vapours/util/util_scope_guard.hpp index 203f43d55..b81cd74c4 100644 --- a/libraries/libvapours/include/vapours/util/util_scope_guard.hpp +++ b/libraries/libvapours/include/vapours/util/util_scope_guard.hpp @@ -30,10 +30,10 @@ namespace ams::util { bool active; public: constexpr ALWAYS_INLINE ScopeGuard(F f) : f(std::move(f)), active(true) { } - ALWAYS_INLINE ~ScopeGuard() { if (active) { f(); } } - ALWAYS_INLINE void Cancel() { active = false; } + constexpr ALWAYS_INLINE ~ScopeGuard() { if (active) { f(); } } + constexpr ALWAYS_INLINE void Cancel() { active = false; } - ALWAYS_INLINE ScopeGuard(ScopeGuard&& rhs) : f(std::move(rhs.f)), active(rhs.active) { + constexpr ALWAYS_INLINE ScopeGuard(ScopeGuard&& rhs) : f(std::move(rhs.f)), active(rhs.active) { rhs.Cancel(); } diff --git a/libraries/libvapours/source/test/test_intrusive_red_black_tree.cpp b/libraries/libvapours/source/test/test_intrusive_red_black_tree.cpp new file mode 100644 index 000000000..1f6a94406 --- /dev/null +++ b/libraries/libvapours/source/test/test_intrusive_red_black_tree.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +/* TODO: Define to enable tests? */ +#if 0 + +namespace ams::test { + + template + concept IsRedBlackTreeTestNode = std::constructible_from && requires (T &t, const T &ct) { + { ct.GetValue() } -> std::same_as; + { t.GetNode() } -> std::same_as< util::IntrusiveRedBlackTreeNode &>; + { ct.GetNode() } -> std::same_as; + }; + + template requires IsRedBlackTreeTestNode + struct TestComparator { + using RedBlackKeyType = int; + + static constexpr int Compare(const T &lhs, const T &rhs) { + if (lhs.GetValue() < rhs.GetValue()) { + return -1; + } else if (lhs.GetValue() > rhs.GetValue()) { + return 1; + } else { + return 0; + } + } + + static constexpr int Compare(const int &lhs, const T &rhs) { + if (lhs < rhs.GetValue()) { + return -1; + } else if (lhs > rhs.GetValue()) { + return 1; + } else { + return 0; + } + } + }; + + class TestBaseNode : public util::IntrusiveRedBlackTreeBaseNode { + private: + const int m_value; + public: + constexpr TestBaseNode(int value) : m_value(value) { /* ... */ } + + constexpr int GetValue() const { return m_value; } + + constexpr util::IntrusiveRedBlackTreeNode &GetNode() { return static_cast< util::IntrusiveRedBlackTreeNode &>(*this); } + constexpr const util::IntrusiveRedBlackTreeNode &GetNode() const { return static_cast(*this); } + }; + static_assert(IsRedBlackTreeTestNode); + + class TestTreeTypes; + + class TestMemberNode { + private: + friend class TestTreeTypes; + private: + const int m_value; + util::IntrusiveRedBlackTreeNode m_node; + public: + constexpr TestMemberNode(int value) : m_value(value), m_node() { /* ... */ } + + constexpr int GetValue() const { return m_value; } + + constexpr util::IntrusiveRedBlackTreeNode &GetNode() { return m_node; } + constexpr const util::IntrusiveRedBlackTreeNode &GetNode() const { return m_node; } + }; + static_assert(IsRedBlackTreeTestNode); + + class TestTreeTypes { + public: + using BaseTree = util::IntrusiveRedBlackTreeBaseTraits::TreeType>; + using MemberTree = util::IntrusiveRedBlackTreeMemberTraits<&TestMemberNode::m_node>::TreeType>; + }; + + using TestBaseTree = TestTreeTypes::BaseTree; + using TestMemberTree = TestTreeTypes::MemberTree; + + template + consteval bool TestUsage() { + constexpr int Values[] = { -3, 0, 5, 7, 11111111, 924, -100, 68, 70, 69, }; + + /* Get sorted array. */ + std::array sorted_values{}; + std::copy(std::begin(Values), std::end(Values), std::begin(sorted_values)); + std::sort(std::begin(sorted_values), std::end(sorted_values)); + + /* Create the tree. */ + Tree tree{}; + AMS_ASSUME(tree.begin() == tree.end()); + + /* Create a node for each value. */ + /* TODO: GCC bug in constant evaluation fails if we use constexpr new/dynamically allocated nodes. */ + /* Check if this works in gcc 11. */ + std::array nodes = [&](std::index_sequence) { + return std::array { Node(Values[Ix])... }; + }(std::make_index_sequence()); + + /* Insert each node into the tree. */ + for (size_t i = 0; i < util::size(Values); ++i) { + tree.insert(nodes[i]); + if (std::distance(tree.begin(), tree.end()) != static_cast(i + 1)) { + return false; + } + } + + /* Verify that the nodes are in sorted order. */ + { + size_t i = 0; + for (const auto &node : tree) { + if (node.GetValue() != sorted_values[i++]) { + return false; + } + } + } + + /* Verify correctness with begin() */ + { + size_t i = 0; + for (auto it = tree.begin(); it != tree.end(); ++it) { + if (it->GetValue() != sorted_values[i++]) { + return false; + } + } + } + + /* Verify correctness with cbegin() */ + { + size_t i = 0; + for (auto it = tree.cbegin(); it != tree.cend(); ++it) { + if (it->GetValue() != sorted_values[i++]) { + return false; + } + } + } + + /* Verify min/max. */ + if (tree.front().GetValue() != sorted_values[0]) { + return false; + } + if (tree.back().GetValue() != sorted_values[sorted_values.size() - 1]) { + return false; + } + + /* Remove a value. */ + tree.erase(tree.iterator_to(nodes[3])); + + /* Verify nodes are in sorted order. */ + { + size_t i = 0; + for (const auto &node : tree) { + if (node.GetValue() == nodes[3].GetValue()) { + return false; + } + + if (node.GetValue() != sorted_values[i++]) { + if (node.GetValue() != sorted_values[i++]) { + return false; + } + } + } + } + + /* Add the node back. */ + tree.insert(nodes[3]); + + /* Verify nodes are in sorted order. */ + { + size_t i = 0; + for (const auto &node : tree) { + if (node.GetValue() != sorted_values[i++]) { + return false; + } + } + } + + /* Verify that find works. */ + for (size_t i = 0; i < util::size(Values); ++i) { + if (tree.find(Node(Values[i])) != tree.iterator_to(nodes[i])) { + return false; + } + if (tree.nfind(Node(sorted_values[i]))->GetValue() != sorted_values[i]) { + return false; + } + if (tree.find_key(Values[i]) != tree.iterator_to(nodes[i])) { + return false; + } + if (tree.nfind_key(sorted_values[i])->GetValue() != sorted_values[i]) { + return false; + } + } + + if (tree.find(Node(std::numeric_limits::min())) != tree.end()) { + return false; + } + + /* Verify that nfind works. */ + for (size_t i = 0; i < util::size(Values) - 1; ++i) { + if (tree.nfind(Node(sorted_values[i] + 1))->GetValue() != sorted_values[i + 1]) { + return false; + } + if (tree.nfind_key(sorted_values[i] + 1)->GetValue() != sorted_values[i + 1]) { + return false; + } + } + + return true; + } + + static_assert(TestUsage()); + static_assert(TestUsage()); + + +} + +#endif