xo-ordinaltree: add _gc_assign_member feature to gc-aware allocs

This commit is contained in:
Roland Conybeare 2025-12-04 17:33:40 -05:00
commit c56dd72292
9 changed files with 488 additions and 145 deletions

View file

@ -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 <typename T, typename Allocator>
void _gc_assign_member(T ** lhs,
T * rhs,
Allocator & alloc)
{
static_assert(std::is_convertible_v<decltype(*lhs), IObject*>);
alloc.mm_->assign_member(this, reinterpret_cast<IObject **>(lhs), rhs);
}
/** true iff this object represents a forwarding pointer.
* Forwarding pointers are exclusively created by the garbage collector;

View file

@ -7,6 +7,7 @@
#include <type_traits>
#include <memory>
#include <cassert>
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<Allocator>
* 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<xo::gc::GC>)
* 3. allows a gc-aware template class T to fallback
* to ordinary allocator-aware behavior for non-gc
* allocators, such as std::allocator<T>,
* 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 <typename A>
struct has_incremental_gc_interface<A, std::void_t<typename A::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

View file

@ -66,6 +66,7 @@ namespace xo {
using key_type = Key;
using mapped_type = Value;
using value_type = std::pair<Key const, Value>;
// using key_compare = Compare // not yet
using allocator_type = Allocator;
using allocator_traits = xo::gc::gc_allocator_traits<Allocator>;
@ -81,6 +82,10 @@ namespace xo {
using node_allocator_type = allocator_traits::template rebind_alloc<node_type>;
using node_allocator_traits = xo::gc::gc_allocator_traits<node_allocator_type>;
/* for make() factory method */
using tree_allocator_type = allocator_traits::template rebind_alloc<RedBlackTree>;
using tree_allocator_traits = xo::gc::gc_allocator_traits<tree_allocator_type>;
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<bool, RbNode *> insert_result
= RbUtil::template insert_aux<node_allocator_type>(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 << "<RedBlackTree>";
}
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);

View file

@ -321,7 +321,7 @@ namespace xo {
os << "<Node>";
}
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) {

View file

@ -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;

View file

@ -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 */

View file

@ -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<Testcase_RbTree>
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 = RedBlackTree<int,
@ -103,6 +110,10 @@ namespace xo {
}
}
/* test RbTree with gc allocator.
*
* do lots of GC-requesting, to create old->new cross-generational pointers
*/
TEST_CASE("rbtree-gc-1", "[gc][redblacktree]")
{
using RbTree = RedBlackTree<int,
@ -118,13 +129,16 @@ namespace xo {
std::uint64_t seed = 8813374093428528487ULL;
auto rgen = xo::rng::xoshiro256ss(seed);
for (std::uint32_t n=0; n<1; ++n) {
//for (std::uint32_t n=0; n<1024; ++n)
for (std::uint32_t n=0; n<=128;) {
bool ok_flag = false;
for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) {
bool debug_flag = c_debug_flag || (attention == 1);
scope log(XO_DEBUG2(debug_flag, "rbtree-gc-1"));
scope log(XO_DEBUG2(debug_flag, "rbtree-gc-1"), xtag("i_tc", i_tc), xtag("n", n));
INFO(tostr(xtag("i_tc", i_tc), xtag("n", n)));
ok_flag = true; // unless contradicted below
@ -137,16 +151,152 @@ namespace xo {
.debug_flag_ = debug_flag
}
);
REQUIRE(gc.get());
gc->disable_gc();
xo::gc::allocator<RbTree::node_type> allocator(gc.get());
REQUIRE(gc->native_gc_statistics().n_gc() == 0);
RbTree rbtree(allocator, c_debug_flag);
xo::gc::allocator<RbTree> allocator(gc.get());
#ifdef NOT_YET
/* insert [0..n-1] in random order **/
ok_flag &= TreeUtil<RbTree>::random_inserts(n, debug_flag, &rgen, &rbtree);
#endif
gp<RbTree> 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<RbTree>::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<RbTree>::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<RbTree>::check_ordinal_lookup(0 /*dvalue*/,
debug_flag,
*rbtree);
/* verify end-to-end iteration */
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(0,
debug_flag,
*rbtree);
/* check reduced sums for each cut */
ok_flag &= TreeUtil<RbTree>::check_reduced_sum(0,
debug_flag,
*rbtree);
/* verify read-only variant of operator[] */
ok_flag &= TreeUtil<RbTree>::random_lookups(debug_flag,
*rbtree,
&rgen);
/* re-verify lookup on (better-be-monotonic) reduced sums
* remove doubt that operator[] changed something.
*/
ok_flag &= TreeUtil<RbTree>::check_ordinal_lookup(0 /*dvalue*/,
debug_flag,
*rbtree);
/* re-verify end-to-end iteration, so we can say we did */
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(0,
debug_flag,
*rbtree);
/* make random updates, along with basic consistency checks */
ok_flag &= TreeUtil<RbTree>::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<RbTree>::check_ordinal_lookup(10000 /*dvalue*/,
debug_flag,
*rbtree);
/* verify end-to-end iteration */
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(10000,
debug_flag,
*rbtree);
/* check reduced sums for each cut */
ok_flag &= TreeUtil<RbTree>::check_reduced_sum(10000,
debug_flag,
*rbtree);
/* verify behavior of read/write variant of operator[] */
ok_flag &= TreeUtil<RbTree>::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;
}
}

View file

@ -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<std::uint32_t> u(n);
for(std::uint32_t i=0; i<n; ++i)
u[i] = i;
u[i] = lo + i*k;
/* shuffle to get unpredictable insert order */
std::shuffle(u.begin(), u.end(), *p_rgen);
size_t tree_z0 = p_tree->size();
/* 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<uint32_t> 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*/

View file

@ -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<uint32_t> u
= Util::random_permutation(n, p_rgen);
std::size_t n = p_rbtree->size();
std::vector<uint32_t> 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<uint32_t> 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<RbTree>::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<RbTree>::random_removes(debug_flag, &rgen, &rbtree);
}
log.end_scope();