diff --git a/xo-allocutil/include/xo/allocutil/IObject.hpp b/xo-allocutil/include/xo/allocutil/IObject.hpp index c6f9611d..086d4135 100644 --- a/xo-allocutil/include/xo/allocutil/IObject.hpp +++ b/xo-allocutil/include/xo/allocutil/IObject.hpp @@ -21,6 +21,24 @@ namespace xo { public: /** impl inheriting this class must provide gc hooks **/ static constexpr bool _requires_gc_hooks = true; + /** impl inheriting this class must use write barriers + * (so that GC allocator can remember cross-generational pointers) + **/ + static constexpr bool _requires_write_barrier = true; + + /** GC write barrier: + * assign value @p rhs to member @p *lhs of @p parent. + * Identifiy and remember cross-generational pointers. + **/ + template + void _gc_assign_member(T ** lhs, + T * rhs, + Allocator & alloc) + { + static_assert(std::is_convertible_v); + + alloc.mm_->assign_member(this, reinterpret_cast(lhs), rhs); + } /** true iff this object represents a forwarding pointer. * Forwarding pointers are exclusively created by the garbage collector; diff --git a/xo-allocutil/include/xo/allocutil/gc_allocator_traits.hpp b/xo-allocutil/include/xo/allocutil/gc_allocator_traits.hpp index 80184b0c..6ddda37b 100644 --- a/xo-allocutil/include/xo/allocutil/gc_allocator_traits.hpp +++ b/xo-allocutil/include/xo/allocutil/gc_allocator_traits.hpp @@ -7,6 +7,7 @@ #include #include +#include namespace xo { namespace gc { @@ -15,7 +16,18 @@ namespace xo { * Introduces additional i/face methods * for garbage-collector-enabled allocators * - * allocator A can identify itself as a copying collector: + * Use Cases: + * 1. drop-in replacement for std::allocator_traits + * with non-gc-aware allocators. + * 2. allows a gc-aware template class to activate + * gc support when used with a collecting allocator + * (i.e. xo::gc::allocator) + * 3. allows a gc-aware template class T to fallback + * to ordinary allocator-aware behavior for non-gc + * allocators, such as std::allocator, + * but also pool allocators etc. + * + * An allocator A can identify itself as a copying collector: * * 1. provide A::object_interface * per-object header interface: tells garbage collector @@ -33,7 +45,24 @@ namespace xo { * - xo::gc::GC has a collection API and also provides * garbage collection * - * GC-allocated objects must: + * GC object model + * 2a. A GC-allocated object is an object that GC manages + * atomically. All memory associated with a GC-allocated + * object has the same lifetime. + * 2b. A GC-allocation is 1:1 with a GC-allocated object + * 2c. A GC-allocated object may have internal pointers. + * These are pointer interior to the same original + * allocation. It's the responsibility of the object to update these + * (if/when GC moves said object) via GC hooks. + * 2d. A GC-allocated object may have external pointers + * to other GC-allocated objects. Managing these is split + * between GC and object itself. GC takes responsibility + * for moving the destination objects. + * Object is responsible for telling GC about such pointers + * and changes to their values + * (e.g. IObject::_forward_children()) + * + * GC object implementation: gc objects must: * 2a. inherit A::object_interface * 2b. implement A::object_interface::_shallow_size() * 2c. implement A::object_interface::_shallow_copy(alloc) @@ -41,6 +70,22 @@ namespace xo { * in multiple inheritance scenarios * 2e. implement A::object_interface::_offset_destination(src) * + * 3. write barrier support: + * A generational GC needs to track changes that create or modify + * inter-generational pointers. + * + * GC-aware classes could write: + * MyClass::update_pointer_state(IObject *new_value, gc::IAlloc *gc) { + * if constexpr (GcObjectInterface::_requires_write_barrier) { + * gc->assign_member(this, &some_member_, new_value); + * } else { + * this->some_member_ = new_value; + * } + * } + * + * but simpler: + * GcObjectInterface::_gc_assign_member(this, &some_member_, new_value, alloc_); + * * Design Notes: * - virtual-method choice requires vtable pointer per object; * but zero *marginal* space cost for types that would have @@ -68,7 +113,7 @@ namespace xo { // opt-in: A provides nested type 'has_incremental_collector_interface': // struct A { - // using is_incremental_collector = std::true_type; + // using has_incremental_collector = std::true_type; // }; template struct has_incremental_gc_interface> : @@ -110,6 +155,8 @@ namespace xo { *lhs = rhs; } + virtual bool _is_forwarded() const { return false; } + virtual std::size_t _shallow_size() const { assert(false); return 0; } }; // specialization when A provides gc_object_interface diff --git a/xo-ordinaltree/include/xo/ordinaltree/RedBlackTree.hpp b/xo-ordinaltree/include/xo/ordinaltree/RedBlackTree.hpp index d4d6a568..36fbad36 100644 --- a/xo-ordinaltree/include/xo/ordinaltree/RedBlackTree.hpp +++ b/xo-ordinaltree/include/xo/ordinaltree/RedBlackTree.hpp @@ -66,6 +66,7 @@ namespace xo { using key_type = Key; using mapped_type = Value; using value_type = std::pair; + // using key_compare = Compare // not yet using allocator_type = Allocator; using allocator_traits = xo::gc::gc_allocator_traits; @@ -81,6 +82,10 @@ namespace xo { using node_allocator_type = allocator_traits::template rebind_alloc; using node_allocator_traits = xo::gc::gc_allocator_traits; + /* for make() factory method */ + using tree_allocator_type = allocator_traits::template rebind_alloc; + using tree_allocator_traits = xo::gc::gc_allocator_traits; + using Direction = detail::Direction; using size_type = std::size_t; using difference_type = std::ptrdiff_t; @@ -118,16 +123,19 @@ namespace xo { {} #endif - static RedBlackTree * make(const allocator_type & alloc = allocator_type{}, + static RedBlackTree * make(allocator_type kvpair_alloc = allocator_type{}, bool debug_flag = false) { - RedBlackTree * tree = allocator_traits::allocate(alloc, 1); + tree_allocator_type tree_alloc(kvpair_alloc); + + RedBlackTree * tree = tree_allocator_traits::allocate(tree_alloc, 1); + try { // placement new - allocator_traits::construct(alloc, tree, alloc, debug_flag); + tree_allocator_traits::construct(tree_alloc, tree, kvpair_alloc, debug_flag); return tree; } catch(...) { - allocator_traits::deallocate(alloc, tree, 1); + tree_allocator_traits::deallocate(tree_alloc, tree, 1); } return nullptr; @@ -314,6 +322,8 @@ namespace xo { */ RbTreeConstLhs operator[](Key const & k) const { + //scope log(XO_DEBUG(true), xtag("variant", "readonly"), xtag("key", k)); + RbNode const * node = RbUtil::find(this->root_, k); return RbTreeConstLhs(this, node); @@ -347,11 +357,14 @@ namespace xo { * // v.node contents may have been copied and v.node deleted */ RbTreeLhs operator[](Key const & k) { + //scope log(XO_DEBUG(true), tag("variant", "autoinsert"), tag("key", k)); + std::pair insert_result = RbUtil::template insert_aux(this->node_alloc_, value_type(k, Value() /*used iff creating new node*/), false /*allow_replace_flag*/, this->reduce_fn_, + this->debug_flag_, &(this->root_)); return RbTreeLhs(this, insert_result.second, k); @@ -609,7 +622,7 @@ namespace xo { virtual void display(std::ostream & os) const { os << ""; } - virtual std::size_t _shallow_size() const { return sizeof(*this); } + virtual std::size_t _shallow_size() const final override { return sizeof(*this); } virtual IObject * _shallow_copy(gc::IAlloc * gc) const { if constexpr (GcObjectInterface::_requires_gc_hooks) { xo::Cpof cpof(gc, this); diff --git a/xo-ordinaltree/include/xo/ordinaltree/rbtree/Node.hpp b/xo-ordinaltree/include/xo/ordinaltree/rbtree/Node.hpp index 5f530712..676cabcf 100644 --- a/xo-ordinaltree/include/xo/ordinaltree/rbtree/Node.hpp +++ b/xo-ordinaltree/include/xo/ordinaltree/rbtree/Node.hpp @@ -321,7 +321,7 @@ namespace xo { os << ""; } - virtual std::size_t _shallow_size() const { return sizeof(*this); } + virtual std::size_t _shallow_size() const final override { 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) { diff --git a/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTreeLhs.hpp b/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTreeLhs.hpp index 1d4066ff..bbedaa10 100644 --- a/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTreeLhs.hpp +++ b/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTreeLhs.hpp @@ -37,7 +37,7 @@ namespace xo { if (!this->node_) { throw std::runtime_error - (tostr("rbtree: attempt to use empty lhs object as rvalue")); + (tostr("RedBlackTreeLhsBase: attempt to use empty lhs object as rvalue")); } return this->node_->contents().second; diff --git a/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTreeUtil.hpp b/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTreeUtil.hpp index acc162cc..e1c9b74b 100644 --- a/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTreeUtil.hpp +++ b/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTreeUtil.hpp @@ -786,12 +786,12 @@ namespace xo { value_type const & kv_pair, bool allow_replace_flag, Reduce const & reduce_fn, + bool debug_flag, RbNode ** pp_root) { using xo::xtag; - constexpr bool c_debug_flag = false; - //XO_SCOPE2(log, true /*debug_flag*/); + scope log(XO_DEBUG(debug_flag)); RbNode * N = *pp_root; @@ -809,7 +809,7 @@ namespace xo { /* after modifying a node n, must recalculate reductions * along path [root .. n] */ - RbTreeUtil::fixup_ancestor_size(reduce_fn, N, c_debug_flag); + RbTreeUtil::fixup_ancestor_size(reduce_fn, N, debug_flag); //log && log(xtag("path", (char const *)"A")); @@ -843,7 +843,7 @@ namespace xo { assert(is_red(N->child(d))); /* recalculate Node sizes on path [root .. N] */ - RbTreeUtil::fixup_ancestor_size(reduce_fn, N, c_debug_flag); + RbTreeUtil::fixup_ancestor_size(reduce_fn, N, debug_flag); /* after adding a node, must rebalance to restore RB-shape */ RbTreeUtil::fixup_red_shape(d, N, reduce_fn, pp_root); @@ -1700,19 +1700,40 @@ namespace xo { &black_height] (RbNode const *x, uint32_t bd) { + XO_EXPECT(x->_is_forwarded() == false, + tostr(c_self, (": stray forwarding pointer where node expected"), + xtag("i", i_node), xtag("node[i]", x) + )); + /* RB2. if c=x->child(d), then c->parent()=x */ if (x->left_child()) { + XO_EXPECT(x->left_child()->_is_forwarded() == false, + tostr(c_self, (": forwarding pointer where left child expected"), + xtag("i", i_node), xtag("node[i]", x), + xtag("key[i]", x->key()), + xtag("child", x->left_child()) + )); + XO_EXPECT(x == x->left_child()->parent(), tostr(c_self, (": expect symmetric child/parent pointers"), xtag("i", i_node), xtag("node[i]", x), xtag("key[i]", x->key()), xtag("child", x->left_child()), xtag("child.key", x->left_child()->key()), - xtag("child.parent", x->left_child()->parent_))); + xtag("child.parent", x->left_child()->parent_), + xtag("child.parent._is_forwarded", x->left_child()->parent_->_is_forwarded()) + )); } if (x->right_child()) { + XO_EXPECT(x->right_child()->_is_forwarded() == false, + tostr(c_self, (": forwarding pointer where right child expected"), + xtag("i", i_node), xtag("node[i]", x), + xtag("key[i]", x->key()), + xtag("child", x->right_child()) + )); + XO_EXPECT(x == x->right_child()->parent(), tostr(c_self, ": expect symmetric child/parent pointers", xtag("i", i_node), @@ -1720,7 +1741,9 @@ namespace xo { xtag("key[i]", x->key()), xtag("child", x->right_child()), xtag("child.key", x->right_child()->key()), - xtag("child.parent", x->right_child()->parent_))); + xtag("child.parent", x->right_child()->parent_), + xtag("child.parent._is_forwarded", x->right_child()->parent_->_is_forwarded()) + )); } /* RB3. all nodes have the same black-height */ diff --git a/xo-ordinaltree/utest/RedBlackTree-gc.test.cpp b/xo-ordinaltree/utest/RedBlackTree-gc.test.cpp index 5e41c3ea..b888ddbc 100644 --- a/xo-ordinaltree/utest/RedBlackTree-gc.test.cpp +++ b/xo-ordinaltree/utest/RedBlackTree-gc.test.cpp @@ -21,24 +21,31 @@ namespace xo { Testcase_RbTree(std::size_t nz, std::size_t tz, std::size_t ngct, - std::size_t tgct) : nursery_z_{nz}, + std::size_t tgct, + bool do_extra_gc) : nursery_z_{nz}, tenured_z_{tz}, incr_gc_threshold_{ngct}, - full_gc_threshold_{tgct} {} + full_gc_threshold_{tgct}, + do_extra_gc_{do_extra_gc} {} std::size_t nursery_z_; std::size_t tenured_z_; std::size_t incr_gc_threshold_; std::size_t full_gc_threshold_; + bool do_extra_gc_; }; std::vector s_testcase_v = { - Testcase_RbTree(1024, 4096, 512, 512), + //Testcase_RbTree(1024, 4096, 512, 512, false), + Testcase_RbTree(1024, 4096, 512, 512, true), }; } + /* test that we can allocate RbTree::RbNode instances, + * using gc allocator + */ TEST_CASE("rbnode-gc-1", "[gc][redblacktree]") { using RbTree = RedBlackTreenew cross-generational pointers + */ TEST_CASE("rbtree-gc-1", "[gc][redblacktree]") { using RbTree = RedBlackTreedisable_gc(); - xo::gc::allocator allocator(gc.get()); + REQUIRE(gc->native_gc_statistics().n_gc() == 0); - RbTree rbtree(allocator, c_debug_flag); + xo::gc::allocator allocator(gc.get()); -#ifdef NOT_YET - /* insert [0..n-1] in random order **/ - ok_flag &= TreeUtil::random_inserts(n, debug_flag, &rgen, &rbtree); -#endif + gp rbtree = RbTree::make(allocator, c_debug_flag); + + gc->add_gc_root_dwim(&rbtree); + + REQUIRE(rbtree.get() != nullptr); + REQUIRE(rbtree->verify_ok(debug_flag)); + + if (tc.do_extra_gc_) { + REQUIRE(gc->gc_in_progress() == false); + gc->request_gc(gc::generation::nursery); + REQUIRE(gc->is_gc_pending()); + REQUIRE(gc->enable_gc_once()); + REQUIRE(gc->gc_in_progress() == false); + } + + REQUIRE(rbtree->verify_ok(debug_flag)); + + { + INFO("insert phase A - random_inserts(0, n, 2, ..)"); + + /* insert even integers in [0, n), in random order **/ + ok_flag &= TreeUtil::random_inserts(0, n, 2, + debug_flag, + &rgen, + rbtree.get()); + + if (tc.do_extra_gc_) { + REQUIRE(gc->gc_in_progress() == false); + gc->request_gc(gc::generation::nursery); + REQUIRE(gc->is_gc_pending()); + REQUIRE(gc->enable_gc_once()); + REQUIRE(gc->gc_in_progress() == false); + } + } + + if (n > 0) { + { + INFO("insert phase B - random_inserts(1, n+1, ..)"); + + /* insert odd integers in [1, n+1), in random order **/ + ok_flag &= TreeUtil::random_inserts(1, n+1, 2, + debug_flag, + &rgen, + rbtree.get()); + + if (tc.do_extra_gc_) { + REQUIRE(gc->gc_in_progress() == false); + gc->request_gc(gc::generation::nursery); + REQUIRE(gc->is_gc_pending()); + REQUIRE(gc->enable_gc_once()); + REQUIRE(gc->gc_in_progress() == false); + } + } + } + + /* check iterator traverses [0..n-1] in both directions */ + ok_flag &= TreeUtil::check_ordinal_lookup(0 /*dvalue*/, + debug_flag, + *rbtree); + + /* verify end-to-end iteration */ + ok_flag &= TreeUtil::check_bidirectional_iterator(0, + debug_flag, + *rbtree); + + /* check reduced sums for each cut */ + ok_flag &= TreeUtil::check_reduced_sum(0, + debug_flag, + *rbtree); + + /* verify read-only variant of operator[] */ + ok_flag &= TreeUtil::random_lookups(debug_flag, + *rbtree, + &rgen); + + /* re-verify lookup on (better-be-monotonic) reduced sums + * remove doubt that operator[] changed something. + */ + ok_flag &= TreeUtil::check_ordinal_lookup(0 /*dvalue*/, + debug_flag, + *rbtree); + + /* re-verify end-to-end iteration, so we can say we did */ + ok_flag &= TreeUtil::check_bidirectional_iterator(0, + debug_flag, + *rbtree); + + /* make random updates, along with basic consistency checks */ + ok_flag &= TreeUtil::random_updates(10000, + debug_flag, + rbtree.get(), + &rgen); + + if (tc.do_extra_gc_) { + REQUIRE(gc->gc_in_progress() == false); + gc->request_gc(gc::generation::nursery); + REQUIRE(gc->is_gc_pending()); + REQUIRE(gc->enable_gc_once()); + REQUIRE(gc->gc_in_progress() == false); + } + + /* verify that updates changed tree contents in expected way */ + ok_flag &= TreeUtil::check_ordinal_lookup(10000 /*dvalue*/, + debug_flag, + *rbtree); + + /* verify end-to-end iteration */ + ok_flag &= TreeUtil::check_bidirectional_iterator(10000, + debug_flag, + *rbtree); + + /* check reduced sums for each cut */ + ok_flag &= TreeUtil::check_reduced_sum(10000, + debug_flag, + *rbtree); + + /* verify behavior of read/write variant of operator[] */ + ok_flag &= TreeUtil::random_removes(debug_flag, + &rgen, + rbtree.get()); + + if (tc.do_extra_gc_) { + REQUIRE(gc->gc_in_progress() == false); + gc->request_gc(gc::generation::nursery); + REQUIRE(gc->is_gc_pending()); + REQUIRE(gc->enable_gc_once()); + REQUIRE(gc->gc_in_progress() == false); + } + + /* verify iteration one more time -- + * remove doubt w.r.t. + */ + REQUIRE(rbtree->verify_ok(debug_flag)); } + + if (n == 0) + n = 1; + else + n = 2*n; } } diff --git a/xo-ordinaltree/utest/random_tree_ops.hpp b/xo-ordinaltree/utest/random_tree_ops.hpp index 6e8dc743..4cf207fb 100644 --- a/xo-ordinaltree/utest/random_tree_ops.hpp +++ b/xo-ordinaltree/utest/random_tree_ops.hpp @@ -80,11 +80,16 @@ namespace utest { return ok_flag; } /*test_clear*/ - /* do n random inserts (taken from *p_rgen) into *p_rbtreẹ - * inserted keys will be distinct values in [0, .., n-1] + /* do + * n = (hi - lo) / k + * random inserts (taken from *p_rgen) into *p_rbtreẹ + * inserted keys will comprise the distinct values + * {lo, lo+k, lo+2k, ..., lo+n.k} */ static bool - random_inserts(std::uint32_t n, + random_inserts(std::uint32_t lo, + std::uint32_t hi, + std::uint32_t k, bool catch_flag, xo::rng::xoshiro256ss * p_rgen, Tree * p_tree) @@ -93,18 +98,25 @@ namespace utest { bool ok_flag = true; - xo::scope log(XO_DEBUG(catch_flag)); + xo::scope log(XO_DEBUG(catch_flag), xtag("lo", lo), xtag("hi", hi), xtag("k", k)); REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag)); + if ((hi <= lo) || (k == 0)) + return true; + + uint32_t n = (hi - lo) / k; + /* n keys 0..n-1 */ std::vector u(n); for(std::uint32_t i=0; isize(); + /* insert keys according to permutation u */ uint32_t i = 1; for(uint32_t x : u) { @@ -126,11 +138,20 @@ namespace utest { ++i; } - REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == n); + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == tree_z0 + n); return ok_flag; } /*random_inserts*/ + static bool + random_inserts(std::uint32_t n, + bool catch_flag, + xo::rng::xoshiro256ss * p_rgen, + Tree * p_tree) + { + return random_inserts(0, n, 1, catch_flag, p_rgen, p_tree); + } + /* do n random removes (taken from *p_rgen) from *p_rbtree; * assumes *p_rbtree has keys [0 .. n-1] where n=p_rbtreẹsize */ @@ -444,6 +465,102 @@ namespace utest { return ok_flag; } /*check_bidirectional_iterator*/ + + /** Require: + * - tree has keys [0..n-1], where n=treesize() + * - tree valu at key k is dvalue+10*k + * + * @p catch_flag. control behavior at each test assertion. + * true -> log to console + interact with catch2 + * false -> verify iteration behavior for return code. + * + **/ + static bool + check_reduced_sum(uint32_t dvalue, + bool catch_flag, + Tree const & rbtree) + { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(catch_flag)); + + /* -> false if/when check fails */ + bool ok_flag = true; + + size_t const n = rbtree.size(); + + for(size_t i = 0; i < n; ++i) { + /* compute reduction up to key=i */ + double reduced_upto + = rbtree.reduce_lub(i /*key*/, + true /*is_closed*/); + + double reduced = (i+1) * (5*i + dvalue); + + INFO(tostr(xtag("i", i), xtag("n", n), + xtag("tree.reduced_upto", reduced_upto), + xtag("reduced", reduced), + xtag("dvalue", dvalue))); + + auto glb_ix = rbtree.cfind_sum_glb(reduced); + + REQUIRE_ORFAIL(ok_flag, catch_flag, reduced_upto == reduced); + REQUIRE_ORFAIL(ok_flag, catch_flag, glb_ix.is_dereferenceable()); + /* glb_ix is truth-y */ + REQUIRE_ORFAIL(ok_flag, catch_flag, glb_ix); + + REQUIRE_ORFAIL(ok_flag, catch_flag, glb_ix->first == i); + } + + return ok_flag; + } /*check_reduced_sum*/ + + /* Require: + * - *p_rbtree has keys [0..n-1], where n=rbtree.size() + * - for each key k, associated value is 10*k + * + * Promise: + * - for each key k, associated value is dvalue + 10*k + */ + static bool + random_updates(uint32_t dvalue, + bool catch_flag, + Tree * p_rbtree, + xo::rng::xoshiro256ss * p_rgen) + { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(catch_flag)); + + /* -> false if/when check fails */ + bool ok_flag = true; + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_rbtree->verify_ok()); + + std::size_t n = p_rbtree->size(); + std::vector u + = Util::random_permutation(n, p_rgen); + + /* update key/value pairs in permutation order */ + uint32_t i = 1; + for (uint32_t x : u) { + REQUIRE_ORFAIL(ok_flag, catch_flag, (*p_rbtree)[x] == x*10); + + (*p_rbtree)[x] = dvalue + 10*x; + + REQUIRE_ORFAIL(ok_flag, catch_flag, (*p_rbtree)[x] == dvalue + 10*x); + REQUIRE_ORFAIL(ok_flag, catch_flag, p_rbtree->verify_ok()); + /* assignment to existing key does not change tree size */ + REQUIRE_ORFAIL(ok_flag, catch_flag, p_rbtree->size() == n); + ++i; + } + + REQUIRE(p_rbtree->size() == n); + + return ok_flag; + } /*random_updates*/ }; /*TreeUtil*/ } /*namespace utest*/ diff --git a/xo-ordinaltree/utest/redblacktree.cpp b/xo-ordinaltree/utest/redblacktree.cpp index 3e1b15e7..fdc815e7 100644 --- a/xo-ordinaltree/utest/redblacktree.cpp +++ b/xo-ordinaltree/utest/redblacktree.cpp @@ -44,120 +44,93 @@ namespace { } /*check_ordinal_lookup*/ #endif - /* check that RedBlackTree<>::find_sum_glb() works as advertised. - * - * partial sums of v[j] for j<=i will be: - * - * (i+1) . i - * 10 . --------- + ((i+1) . dvalue) - * 2 - * - * = (i+1).(5.i + dvalue) - * - * Require: - * - rbtree has keys [0..n-1], where n=rbtree.size() - * - rbtree value at key k is dvalue+10*k - */ - void - check_reduced_sum(uint32_t dvalue, - RbTree const & rbtree) - { - size_t const n = rbtree.size(); + /* check that RedBlackTree<>::find_sum_glb() works as advertised. + * + * partial sums of v[j] for j<=i will be: + * + * (i+1) . i + * 10 . --------- + ((i+1) . dvalue) + * 2 + * + * = (i+1).(5.i + dvalue) + * + * Require: + * - rbtree has keys [0..n-1], where n=rbtree.size() + * - rbtree value at key k is dvalue+10*k + */ + void + check_reduced_sum(uint32_t dvalue, + RbTree const & rbtree) + { + size_t const n = rbtree.size(); - for(size_t i = 0; i < n; ++i) { - /* compute reduction up to key=i */ - double reduced_upto - = rbtree.reduce_lub(i /*key*/, - true /*is_closed*/); + for(size_t i = 0; i < n; ++i) { + /* compute reduction up to key=i */ + double reduced_upto + = rbtree.reduce_lub(i /*key*/, + true /*is_closed*/); - double reduced = (i+1) * (5*i + dvalue); + double reduced = (i+1) * (5*i + dvalue); - INFO(tostr(xtag("i", i), xtag("n", n), - xtag("tree.reduced_upto", reduced_upto), - xtag("reduced", reduced), - xtag("dvalue", dvalue))); + INFO(tostr(xtag("i", i), xtag("n", n), + xtag("tree.reduced_upto", reduced_upto), + xtag("reduced", reduced), + xtag("dvalue", dvalue))); - auto glb_ix = rbtree.cfind_sum_glb(reduced); + auto glb_ix = rbtree.cfind_sum_glb(reduced); - REQUIRE(reduced_upto == reduced); + REQUIRE(reduced_upto == reduced); - REQUIRE(glb_ix.is_dereferenceable()); - /* glb_ix is truth-y */ - REQUIRE(glb_ix); + REQUIRE(glb_ix.is_dereferenceable()); + /* glb_ix is truth-y */ + REQUIRE(glb_ix); - REQUIRE(glb_ix->first == i); - } - } /*check_reduced_sum*/ + REQUIRE(glb_ix->first == i); + } + } /*check_reduced_sum*/ -#ifdef OBSOLETE - /* Require: - * - *p_rbtree has keys [0..n-1], where n=rbtree.size() - * - for each key k, associated value is 10*k - */ - void - random_lookups(RbTree const & rbtree, - xoshiro256ss * p_rgen) - { - REQUIRE(rbtree.verify_ok()); + /* Require: + * - *p_rbtree has keys [0..n-1], where n=rbtree.size() + * - for each key k, associated value is 10*k + * + * Promise: + * - for each key k, associated value is dvalue + 10*k + */ + void + random_updates(uint32_t dvalue, + RbTree * p_rbtree, + xoshiro256ss * p_rgen) + { + REQUIRE(p_rbtree->verify_ok()); - size_t n = rbtree.size(); - std::vector u - = Util::random_permutation(n, p_rgen); + std::size_t n = p_rbtree->size(); + std::vector u + = Util::random_permutation(n, p_rgen); - /* lookup keys in permutation order */ - uint32_t i = 1; - for (uint32_t x : u) { - INFO(tostr(xtag("i", i), xtag("n", n), xtag("x", x))); + /* update key/value pairs in permutation order */ + uint32_t i = 1; + for (uint32_t x : u) { + REQUIRE((*p_rbtree)[x] == x*10); - REQUIRE(rbtree[x] == x*10); - REQUIRE(rbtree.verify_ok()); - REQUIRE(rbtree.size() == n); - ++i; - } + (*p_rbtree)[x] = dvalue + 10*x; - REQUIRE(rbtree.size() == n); - } /*random_lookups*/ -#endif + REQUIRE((*p_rbtree)[x] == dvalue + 10*x); + REQUIRE(p_rbtree->verify_ok()); + /* assignment to existing key does not change tree size */ + REQUIRE(p_rbtree->size() == n); + ++i; + } - /* Require: - * - *p_rbtree has keys [0..n-1], where n=rbtree.size() - * - for each key k, associated value is 10*k - * - * Promise: - * - for each key k, associated value is dvalue + 10*k - */ - void - random_updates(uint32_t dvalue, - RbTree * p_rbtree, - xoshiro256ss * p_rgen) - { - REQUIRE(p_rbtree->verify_ok()); + REQUIRE(p_rbtree->size() == n); + } /*random_updates*/ - std::size_t n = p_rbtree->size(); - std::vector u - = Util::random_permutation(n, p_rgen); - - /* update key/value pairs in permutation order */ - uint32_t i = 1; - for (uint32_t x : u) { - REQUIRE((*p_rbtree)[x] == x*10); - - (*p_rbtree)[x] = dvalue + 10*x; - - REQUIRE((*p_rbtree)[x] == dvalue + 10*x); - REQUIRE(p_rbtree->verify_ok()); - /* assignment to existing key does not change tree size */ - REQUIRE(p_rbtree->size() == n); - ++i; - } - - REQUIRE(p_rbtree->size() == n); - } /*random_updates_1*/ - - TEST_CASE("rbtree", "[redblacktree]") { + TEST_CASE("rbtree", "[redblacktree]") + { constexpr bool c_debug_flag = false; RbTree rbtree{RbTree::allocator_type{}, c_debug_flag}; + REQUIRE(rbtree.size() == 0); + std::uint64_t seed = 14950349842636922572UL; /* can reseed from /dev/urandom with: */ //arc4random_buf(&seed, sizeof(seed)); @@ -216,6 +189,7 @@ namespace { debug_flag, rbtree); + /* similarly reverify end-to-end iteration */ ok_flag &= TreeUtil::check_bidirectional_iterator(0, debug_flag, rbtree); @@ -235,6 +209,7 @@ namespace { check_reduced_sum(10000, rbtree); /* verify behavior of read/write variant of operator[] */ ok_flag &= TreeUtil::random_removes(debug_flag, &rgen, &rbtree); + } log.end_scope();