From 72e8db30e1f3ed965ac6cd0098f49f3f26ecc38d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Dec 2025 15:36:59 -0500 Subject: [PATCH] xo-alloc / xo-ordinaltree: + concepts + allocator-aware --- xo-alloc/include/xo/alloc/Object.hpp | 5 +- xo-alloc/utest/GC.test.cpp | 2 +- xo-allocutil/include/xo/allocutil/IObject.hpp | 2 + .../include/xo/allocutil/ObjectVisitor.hpp | 2 +- .../include/xo/ordinaltree/RedBlackTree.hpp | 45 +----- .../include/xo/ordinaltree/rbtree/Node.hpp | 128 ++++++++++-------- .../include/xo/ordinaltree/rbtree/RbTypes.hpp | 63 +++++++++ xo-ordinaltree/utest/RedBlackTree-gc.test.cpp | 83 +++++++++--- xo-reflect/include/xo/reflect/Object.hpp | 1 + 9 files changed, 209 insertions(+), 122 deletions(-) diff --git a/xo-alloc/include/xo/alloc/Object.hpp b/xo-alloc/include/xo/alloc/Object.hpp index 9681afab..d2e742a0 100644 --- a/xo-alloc/include/xo/alloc/Object.hpp +++ b/xo-alloc/include/xo/alloc/Object.hpp @@ -38,7 +38,7 @@ namespace xo { return dynamic_cast(x.ptr()); } - virtual ~Object() = default; + virtual ~Object() noexcept = default; /** memory allocator for objects. Likely this will be a GC instance, * but simple arena also supported. @@ -129,6 +129,9 @@ namespace xo { virtual std::size_t _forward_children(gc::IAlloc * gc) override = 0; }; + static_assert(std::is_destructible_v, "Object must be destructible"); + static_assert(std::is_nothrow_destructible_v, "Object must be noexcept destructible"); + template void Object::assign_member(gp parent, gp * lhs, gp rhs) diff --git a/xo-alloc/utest/GC.test.cpp b/xo-alloc/utest/GC.test.cpp index 03b5c50a..90e22dfa 100644 --- a/xo-alloc/utest/GC.test.cpp +++ b/xo-alloc/utest/GC.test.cpp @@ -200,7 +200,7 @@ namespace xo { //: member2_{mem2, vector_allocator_type(alloc)}, ctor_ran_{true} {} vector_type member2_; - std::size_t ctor_ran_ = false; + bool ctor_ran_ = false; }; } diff --git a/xo-allocutil/include/xo/allocutil/IObject.hpp b/xo-allocutil/include/xo/allocutil/IObject.hpp index 21048884..c6f9611d 100644 --- a/xo-allocutil/include/xo/allocutil/IObject.hpp +++ b/xo-allocutil/include/xo/allocutil/IObject.hpp @@ -96,6 +96,8 @@ namespace xo { virtual std::size_t _forward_children(gc::IAlloc * gc) = 0; }; + static_assert(std::is_destructible_v, "IObject must be destructible"); + /** @class Cpof * @brief argument to operator new used for garbage collector evacuation phase * diff --git a/xo-allocutil/include/xo/allocutil/ObjectVisitor.hpp b/xo-allocutil/include/xo/allocutil/ObjectVisitor.hpp index 1719ef70..276df554 100644 --- a/xo-allocutil/include/xo/allocutil/ObjectVisitor.hpp +++ b/xo-allocutil/include/xo/allocutil/ObjectVisitor.hpp @@ -34,7 +34,7 @@ namespace xo { **/ template class ObjectVisitor { - //void forward_children(T & target, IAlloc * gc) { (void)target; (void)gc; } + void forward_children(T & target, IAlloc * gc) { (void)target; (void)gc; } }; #define XO_TRIVIAL_OBJECT_VISITOR(TYPE) \ diff --git a/xo-ordinaltree/include/xo/ordinaltree/RedBlackTree.hpp b/xo-ordinaltree/include/xo/ordinaltree/RedBlackTree.hpp index cd46ad6c..52f448ec 100644 --- a/xo-ordinaltree/include/xo/ordinaltree/RedBlackTree.hpp +++ b/xo-ordinaltree/include/xo/ordinaltree/RedBlackTree.hpp @@ -25,47 +25,6 @@ namespace xo { namespace tree { - /* concept for the 'Reduce' argument to RedBlackTree<...> - * - * here: - * T = class implementing reduce feature, e.g. SumReduce<...> - * T::value_type = type for output of reduce function. - * - * Value = value_type for rb-tree that supports ordinal statistics - * - * e.g. - * struct ReduceCountAndSum { - * using value_type = std::pair: - * - * value_type nil() { return value_type(0, 0); } - * value_type operator()(value_type const & acc, int64_t val) - * { return value_type(acc.first + val.first, acc.second + val.second); } - * value_type operator()(value_type const & a1, value_type const & a2) - * { return value_type(a1.first + a2.first, a1.second + a2.second); } - * }; - * - * Reduce.nil() -> nominal reduction i.e. reduce on empty set - * Reduce.leaf(v) -> reduction on set {v} - * - * in general: at some internal node, tree splits set of key/value pairs on some key k1, - * with a left subtree lh, and a right subtree rh. - * - * for a binary tree we want to maintain: - * - r1: reduce applied to collection - * lh + {k1} = reduce(reduce(lh), k1) - * - r2: reduce applied to collection - * lh + {k1} + rh = reduce.combine(r1, reduce(r2)) - * - */ - template - concept ReduceConcept = requires(T r, Value v, typename T::value_type a) { - typename T::value_type; - { r.nil() } -> std::same_as; - { r.leaf(v) } -> std::same_as; - { r(a, v) } -> std::same_as; - { r.combine(a, a) } -> std::same_as; - }; - /** @class RedBlackTree * @brief red-black tree with order statistics * @@ -100,8 +59,8 @@ namespace xo { typename Reduce, typename Allocator> class RedBlackTree { - static_assert(ReduceConcept); - //static_assert(requires(Reduce r) { r.nil(); }, "missing .nil() method"); + static_assert(ordered_key); + static_assert(valid_rbtree_reduce_functor); public: using key_type = Key; diff --git a/xo-ordinaltree/include/xo/ordinaltree/rbtree/Node.hpp b/xo-ordinaltree/include/xo/ordinaltree/rbtree/Node.hpp index 6bb719eb..5f530712 100644 --- a/xo-ordinaltree/include/xo/ordinaltree/rbtree/Node.hpp +++ b/xo-ordinaltree/include/xo/ordinaltree/rbtree/Node.hpp @@ -12,6 +12,7 @@ #include "xo/allocutil/IObject.hpp" #include "xo/allocutil/ObjectVisitor.hpp" #include "xo/allocutil/gc_allocator_traits.hpp" +#include #include #include @@ -33,11 +34,13 @@ namespace xo { typename Value, typename Reduce, typename GcObjectInterface> + requires valid_rbtree_node_params class Node : public GcObjectInterface { public: using ReducedValue = typename Reduce::value_type; using ContentsType = std::pair; using value_type = std::pair; + using rvpair_type = std::pair; using Reflect = xo::reflect::Reflect; using TaggedPtr = xo::reflect::TaggedPtr; using IObject = xo::IObject; @@ -45,13 +48,14 @@ namespace xo { public: Node() = default; Node(value_type const & kv_pair, - std::pair const & r) - : color_(C_Red), size_(1), contents_{kv_pair}, reduced_(r) {} + rvpair_type const & rv_pair) + : color_(C_Red), size_(1), contents_{kv_pair}, reduced_(rv_pair) {} Node(value_type && kv_pair, - std::pair && r) + rvpair_type && rv_pair) : color_(C_Red), size_(1), contents_{std::move(kv_pair)}, - reduced_{std::move(r)} {} + reduced_{std::move(rv_pair)} {} + template static Node * make_leaf(NodeAllocator& alloc, @@ -59,12 +63,17 @@ namespace xo { ReducedValue const & leaf_rv) { using traits = xo::gc::gc_allocator_traits; + /* verify Node is constructible. instead of relying on traits::construct */ + static_assert(std::is_constructible_v); + // get memory Node * node = traits::allocate(alloc, 1); try { // placemenent new traits::construct(alloc, node, kv_pair, - std::pair(leaf_rv, leaf_rv)); + rvpair_type(leaf_rv, leaf_rv)); return node; } catch(...) { traits::deallocate(alloc, node, 1); @@ -303,6 +312,60 @@ namespace xo { xtag("r2", this->reduced2())); } /*local_recalc_size*/ + // ----- inherited from GcObjectInterface ----- + + virtual TaggedPtr self_tp() const { + return Reflect::make_tp(const_cast(this)); + } + virtual void display(std::ostream & os) const { + os << ""; + } + + virtual std::size_t _shallow_size() const { return sizeof(*this); } + /* note: only relevant when GcObjectInterface is xo::IObject */ + virtual IObject * _shallow_copy(gc::IAlloc * gc) const { + if constexpr (GcObjectInterface::_requires_gc_hooks) { + xo::Cpof cpof(gc, this); + return new (cpof) Node(*this); + } else { + assert(false && "_shallow_copy assumes gc enabled"); + return nullptr; + } + } + + virtual std::size_t _forward_children(gc::IAlloc * gc) { + if constexpr (GcObjectInterface::_requires_gc_hooks) { + using xo::gc::ObjectVisitor; + + static_assert(std::is_convertible_v, + "parent_ must be convertible to IObject*"); + static_assert(std::is_convertible_v, + "child_v_[0] must be convertible to IObject*"); + + gc->forward_inplace(reinterpret_cast(&parent_)); + gc->forward_inplace(reinterpret_cast(&child_v_[0])); + gc->forward_inplace(reinterpret_cast(&child_v_[1])); + + /* for key, must cast away const so we can forward */ + Key & key = const_cast(contents_.first); + ObjectVisitor::forward_children(key, gc); + + Value & value = contents_.second; + ObjectVisitor::forward_children(value, gc); + + ReducedValue & rv1 = reduced_.first; + ObjectVisitor::forward_children(rv1, gc); + + ReducedValue & rv2 = reduced_.second; + ObjectVisitor::forward_children(rv2, gc); + + return Node::_shallow_size(); + } else { + assert(false && "_forward_children assumes gc enabled"); + return 0ul; + } + } + private: void assign_color(Color x) { this->color_ = x; } void assign_size(size_t z) { this->size_ = z; } @@ -345,60 +408,6 @@ namespace xo { } } /*replace_child_reparent*/ - // ----- inherited from GcObjectInterface ----- - - virtual TaggedPtr self_tp() const { - return Reflect::make_tp(const_cast(this)); - } - virtual void display(std::ostream & os) const { - os << ""; - } - - virtual std::size_t _shallow_size() const { return sizeof(*this); } - /* note: only relevant when GcObjectInterface is xo::IObject */ - virtual IObject * _shallow_copy(gc::IAlloc * gc) const { - if constexpr (GcObjectInterface::_requires_gc_hooks) { - xo::Cpof cpof(gc, this); - return new (cpof) Node(*this); - } else { - assert(false && "_shallow_copy assumes gc enabled"); - return nullptr; - } - } - - virtual std::size_t _forward_children(gc::IAlloc * gc) { - if constexpr (GcObjectInterface::_requires_gc_hooks) { - using xo::gc::ObjectVisitor; - - static_assert(std::is_convertible_v, - "parent_ must be convertible to IObject*"); - static_assert(std::is_convertible_v, - "child_v_[0] must be convertible to IObject*"); - - gc->forward_inplace(reinterpret_cast(&parent_)); - gc->forward_inplace(reinterpret_cast(&child_v_[0])); - gc->forward_inplace(reinterpret_cast(&child_v_[1])); - - /* must cast away const so we can forward */ - Key & key = const_cast(contents_.first); - ObjectVisitor::forward_children(key, gc); - - Value & value = contents_.second; - ObjectVisitor::forward_children(value, gc); - - ReducedValue & rv1 = reduced_.first; - ObjectVisitor::forward_children(rv1, gc); - - ReducedValue & rv2 = reduced_.second; - ObjectVisitor::forward_children(rv2, gc); - - return Node::_shallow_size(); - } else { - assert(false && "_forward_children assumes gc enabled"); - return 0ul; - } - } - private: friend class RbTreeUtil; template @@ -443,6 +452,7 @@ namespace xo { */ std::array child_v_ = {nullptr, nullptr}; }; /*Node*/ + } /*namespace detail*/ } /*namespace tree*/ } /*namespace xo*/ diff --git a/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTypes.hpp b/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTypes.hpp index 24fd5b54..23abec1c 100644 --- a/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTypes.hpp +++ b/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTypes.hpp @@ -62,6 +62,69 @@ namespace xo { IL_AfterEnd, }; /*IteratorLocation*/ } /*namespace detail*/ + + template + concept ordered_key = (std::copyable + && std::default_initializable + && std::totally_ordered); + + template + concept valid_rbtree_node_value = (std::copyable + && std::default_initializable); + + /* concept for the 'Reduce' argument to RedBlackTree<...> + * + * here: + * T = class implementing reduce feature, e.g. SumReduce<...> + * T::value_type = type for output of reduce function. + * + * Value = value_type for rb-tree that supports ordinal statistics + * + * e.g. + * struct ReduceCountAndSum { + * using value_type = std::pair: + * + * value_type nil() { return value_type(0, 0); } + * value_type operator()(value_type const & acc, int64_t val) + * { return value_type(acc.first + val.first, acc.second + val.second); } + * value_type operator()(value_type const & a1, value_type const & a2) + * { return value_type(a1.first + a2.first, a1.second + a2.second); } + * }; + * + * Reduce.nil() -> nominal reduction i.e. reduce on empty set + * Reduce.leaf(v) -> reduction on set {v} + * + * in general: at some internal node, tree splits set of key/value pairs on some key k1, + * with a left subtree lh, and a right subtree rh. + * + * for a binary tree we want to maintain: + * - r1: reduce applied to collection + * lh + {k1} = reduce(reduce(lh), k1) + * - r2: reduce applied to collection + * lh + {k1} + rh = reduce.combine(r1, reduce(r2)) + * + */ + template + concept valid_rbtree_reduce_functor = requires(const Reduce & reduce, + const Value & value, + typename Reduce::value_type const & rv1, + typename Reduce::value_type const & rv2) + { + typename Reduce::value_type; + + { reduce.nil() } -> std::convertible_to; + { reduce.leaf(value) } -> std::convertible_to; + { reduce.combine(rv1, rv2) } -> std::convertible_to; + + requires std::default_initializable; + }; + + template + concept valid_rbtree_node_params = (ordered_key + && valid_rbtree_node_value + && valid_rbtree_reduce_functor + ); + } /*namespace tree*/ } /*namespace xo*/ diff --git a/xo-ordinaltree/utest/RedBlackTree-gc.test.cpp b/xo-ordinaltree/utest/RedBlackTree-gc.test.cpp index aab694b6..5e41c3ea 100644 --- a/xo-ordinaltree/utest/RedBlackTree-gc.test.cpp +++ b/xo-ordinaltree/utest/RedBlackTree-gc.test.cpp @@ -25,7 +25,7 @@ namespace xo { tenured_z_{tz}, incr_gc_threshold_{ngct}, full_gc_threshold_{tgct} {} - + std::size_t nursery_z_; std::size_t tenured_z_; @@ -39,6 +39,70 @@ namespace xo { }; } + TEST_CASE("rbnode-gc-1", "[gc][redblacktree]") + { + using RbTree = RedBlackTree, + xo::gc::allocator>>; + + constexpr bool c_debug_flag = false; + + for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + const Testcase_RbTree & tc = s_testcase_v[i_tc]; + + std::uint64_t seed = 8813374093428528487ULL; + auto rgen = xo::rng::xoshiro256ss(seed); + + for (std::uint32_t n=0; n<1; ++n) { + scope log(XO_DEBUG2(c_debug_flag, "rbtree-gc-1")); + + up gc = GC::make( + { + .initial_nursery_z_ = tc.nursery_z_, + .initial_tenured_z_ = tc.tenured_z_, + .incr_gc_threshold_ = tc.incr_gc_threshold_, + .full_gc_threshold_ = tc.full_gc_threshold_, + .debug_flag_ = c_debug_flag + } + ); + + xo::gc::allocator allocator(gc.get()); + + /* 1. verify that tree node can be constructed. + * if it can't be constructed, the immediately-following construct + * will fail in a non-transparent way. + */ + RbTree::RbNode test_node; + + RbTree::RbNode * test_node_ptr = new (MMPtr(gc.get())) RbTree::RbNode(); + REQUIRE(test_node_ptr); + + if (false) { + // this will compile, but can't will not run unless GC in progress + IObject * copy_ptr = test_node_ptr->_shallow_copy(gc.get()); + } + + /* 2. verify that tree node can be constructed via + * allocator traits. + * + * Reminder: {} expressions won't deduce template arguments + */ + RbTree::node_allocator_traits::construct(allocator, + &test_node, + RbTree::RbNode::value_type{0, 0.0}, + RbTree::RbNode::rvpair_type{0.0, 0.0}); + + /* 3. verify that RbNode::make_leaf() runs */ + RbTree::RbNode * test2_node_ptr + = RbTree::RbNode::make_leaf(allocator, + RbTree::RbNode::value_type{0, 0.0}, + 0.0); + REQUIRE(test2_node_ptr); + } + } + } + TEST_CASE("rbtree-gc-1", "[gc][redblacktree]") { using RbTree = RedBlackTree allocator(gc.get()); - /* 1. verify that tree node can be constructed. - * if it can't be constructed, the immediately-following construct - * will fail in a non-transparent way. - */ - RbTree::RbNode test_node; - -#ifdef NOT_YET - /* 2. verify that tree node can be constructed via - * allocator traits - */ - RbTree::node_allocator_traits::construct(allocator, - &test_node, - {0, 0.0}, - {0.0, 0.0}); - RbTree rbtree(allocator, c_debug_flag); +#ifdef NOT_YET /* insert [0..n-1] in random order **/ ok_flag &= TreeUtil::random_inserts(n, debug_flag, &rgen, &rbtree); #endif @@ -106,4 +156,3 @@ namespace xo { } /*namesapce xo*/ /* end RedBlackTree-gc.test.cpp */ - diff --git a/xo-reflect/include/xo/reflect/Object.hpp b/xo-reflect/include/xo/reflect/Object.hpp index 91a5cedd..768b6058 100644 --- a/xo-reflect/include/xo/reflect/Object.hpp +++ b/xo-reflect/include/xo/reflect/Object.hpp @@ -35,6 +35,7 @@ namespace xo { **/ TypeId type_id_; }; + } /*namespace obj*/ } /*namespace xo*/