xo-ordinaltree: GC test [wip]

This commit is contained in:
Roland Conybeare 2025-12-01 22:25:41 -05:00
commit b03f0be2f2
12 changed files with 274 additions and 53 deletions

View file

@ -80,7 +80,6 @@ add_subdirectory(xo-allocutil)
add_subdirectory(xo-refcnt)
add_subdirectory(xo-subsys)
add_subdirectory(xo-randomgen)
add_subdirectory(xo-ordinaltree)
add_subdirectory(xo-flatstring)
add_subdirectory(xo-pyutil)
add_subdirectory(xo-reflectutil)
@ -90,8 +89,9 @@ add_subdirectory(xo-ratio)
add_subdirectory(xo-unit)
add_subdirectory(xo-pyunit)
add_subdirectory(xo-callback)
#
add_subdirectory(xo-alloc)
add_subdirectory(xo-ordinaltree)
#
add_subdirectory(xo-object)
#
add_subdirectory(xo-webutil)

View file

@ -144,9 +144,17 @@ namespace xo {
using GcCopyCallbackSet = xo::fn::UpCallbackSet<GcCopyCallback>;
using nanos = decltype(xo::qty::qty::nanosecond);
/** rebind is for typed allocators. since IAlloc is untyped,
* we want degenerate version
**/
template <typename U>
struct rebind { using other = GC; };
public:
/** create new GC instance with configuration @p config **/
explicit GC(const Config & config);
/** noncopyable **/
GC(const GC & other) = delete;
virtual ~GC();
/** create GC allocator.

View file

@ -141,19 +141,6 @@ namespace xo {
std::ostream &
operator<< (std::ostream & os, gp<Object> x);
/** @class Cpof
* @brief argument to operator new used for garbage collector evacuation phase
*
* Tag overloaded operator new to activate allocation policy based on location
* in memory of source object.
**/
class Cpof {
public:
explicit Cpof(gc::IAlloc * mm, const Object * src) : mm_{mm}, src_{src} {}
gc::IAlloc * mm_ = nullptr;
const void * src_ = nullptr;
};
} /*namespace xo*/
void * operator new (std::size_t z, const xo::Cpof & copy);

View file

@ -14,6 +14,7 @@ namespace xo {
using up = std::unique_ptr<T>;
class IObject;
class Object;
namespace gc {
/** @class IAllocator
@ -31,10 +32,15 @@ namespace xo {
**/
class IAlloc {
public:
/** type-erased allocator **/
using value_type = std::byte;
using pointer_type = std::byte *;
using size_type = std::size_t;
/** traits: see gc_allocator_traits<IAlloc> **/
using gc_object_interface = xo::IObject;
/** traits: see gc_allocator_traits<IAlloc>
*
* want Object here (not IObject) to get Object::_forward_to()
**/
using gc_object_interface = xo::Object;
using has_incremental_gc_interface = std::true_type;
public:
@ -151,6 +157,66 @@ namespace xo {
// LCOV_EXCL_STOP
}
};
/** allocator wrapper (in the style of std::allocator)
**/
template <typename T>
struct allocator {
public:
using value_type = T;
using pointer = T *;
using const_pointer = const T *;
using reference = T &;
using const_reference = const T &;
using size_type = IAlloc::size_type;
using difference_type = std::ptrdiff_t;
using gc_object_interface = IAlloc::gc_object_interface;
using has_incremental_gc_interface = IAlloc::has_incremental_gc_interface;
/** rebind is for typed allocators. since IAlloc is untyped,
* we want degenerate version
**/
template <typename U>
struct rebind {
using other = allocator<U>;
};
public:
explicit allocator(IAlloc * mm) : mm_{mm} {}
allocator(const allocator &) = default;
allocator & operator=(const allocator &) = default;
template <typename U>
allocator(const allocator<U> & other) : mm_{other.mm_} {}
pointer allocate(size_type n) {
std::byte * raw = mm_->allocate(n * sizeof(T));
return reinterpret_cast<pointer>(raw);
}
void deallocate(pointer p, size_type n) {
std::byte * raw = reinterpret_cast<std::byte *>(p);
mm_->deallocate(raw, n * sizeof(T));
}
// optional construct, destroy (but allocator_traits provides defaults)
/** required! otherwise allocator<T>, allocator<U> with the same IAlloc*
* would be considered to own disjoin memory addresses
**/
template <typename U>
bool operator==(const allocator<U> & other) const noexcept {
return mm_ == other.mm_;
}
public:
IAlloc * mm_ = nullptr;
};
} /*namespace gc*/
class MMPtr {

View file

@ -92,6 +92,20 @@ namespace xo {
**/
virtual std::size_t _forward_children(gc::IAlloc * gc) = 0;
};
/** @class Cpof
* @brief argument to operator new used for garbage collector evacuation phase
*
* Tag overloaded operator new to activate allocation policy based on location
* in memory of source object.
**/
class Cpof {
public:
explicit Cpof(gc::IAlloc * mm, const IObject * src) : mm_{mm}, src_{src} {}
gc::IAlloc * mm_ = nullptr;
const void * src_ = nullptr;
};
}
/* end IObject.hpp */

View file

@ -57,6 +57,10 @@ namespace xo {
using super = std::allocator_traits<Allocator>;
using pointer = typename super::pointer;
using value_type = typename super::value_type;
using super::construct;
using super::destroy;
using super::allocate;
using super::deallocate;
// default: allocator A fallback to standard non-gc allocator behavior
template <typename A, typename = void>

View file

@ -111,21 +111,22 @@ namespace xo {
using allocator_type = Allocator;
using allocator_traits = xo::gc::gc_allocator_traits<Allocator>;
using GcObjectInterface = allocator_traits::template object_interface<Allocator>;
using ReducedValue = typename Reduce::value_type;
using RbUtil = detail::RbTreeUtil<Key, Value, Reduce>;
using RbNode = detail::Node<Key, Value, Reduce>;
using RbUtil = detail::RbTreeUtil<Key, Value, Reduce, GcObjectInterface>;
using RbNode = detail::Node<Key, Value, Reduce, GcObjectInterface>;
using RbTreeLhs = detail::RedBlackTreeLhs<RedBlackTree>;
using RbTreeConstLhs = detail::RedBlackTreeConstLhs<RedBlackTree>;
using node_type = RbNode;
using node_allocator_type = typename xo::gc::gc_allocator_traits<Allocator>::template rebind_alloc<node_type>;
using node_allocator_type = allocator_traits::template rebind_alloc<node_type>;
using node_allocator_traits = xo::gc::gc_allocator_traits<node_allocator_type>;
using Direction = detail::Direction;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using iterator = detail::Iterator<Key, Value, Reduce>;
using const_iterator = detail::ConstIterator<Key, Value, Reduce>;
using iterator = detail::Iterator<Key, Value, Reduce, GcObjectInterface>;
using const_iterator = detail::ConstIterator<Key, Value, Reduce, GcObjectInterface>;
public:
explicit RedBlackTree(const allocator_type & alloc = allocator_type{},
@ -614,10 +615,11 @@ namespace xo {
template <typename Key,
typename Value,
typename Reduce>
typename Reduce,
typename Allocator>
inline std::ostream &
operator<<(std::ostream &os,
RedBlackTree<Key, Value, Reduce> const &tree)
operator<<(std::ostream & os,
RedBlackTree<Key, Value, Reduce, Allocator> const & tree)
{
tree.display();
return os;
@ -626,10 +628,11 @@ namespace xo {
template <typename Key,
typename Value,
typename Reduce,
typename GcObjectInterface,
bool IsConst>
inline std::ostream &
operator<<(std::ostream & os,
detail::IteratorBase<Key, Value, Reduce, IsConst> const & iter)
detail::IteratorBase<Key, Value, Reduce, GcObjectInterface, IsConst> const & iter)
{
iter.print(os);
return os;

View file

@ -16,14 +16,16 @@ namespace xo {
template <typename Key,
typename Value,
typename Reduce,
typename GcObjectInterface,
bool IsConst>
struct NodeTypeTraits { using NodeType = void; };
template <typename Key,
typename Value,
typename Reduce>
struct NodeTypeTraits<Key, Value, Reduce, false> {
using NativeNodeType = Node<Key, Value, Reduce>;
typename Reduce,
typename GcObjectInterface>
struct NodeTypeTraits<Key, Value, Reduce, GcObjectInterface, false> {
using NativeNodeType = Node<Key, Value, Reduce, GcObjectInterface>;
using NodeType = NativeNodeType;
using ContentsType = typename NodeType::value_type;
using NodePtrType = NodeType *;
@ -31,9 +33,10 @@ namespace xo {
template <typename Key,
typename Value,
typename Reduce>
struct NodeTypeTraits<Key, Value, Reduce, true> {
using NativeNodeType = Node<Key, Value, Reduce>;
typename Reduce,
typename GcObjectInterface>
struct NodeTypeTraits<Key, Value, Reduce, GcObjectInterface, true> {
using NativeNodeType = Node<Key, Value, Reduce, GcObjectInterface>;
using NodeType = NativeNodeType const;
using ContentsType = typename NodeType::value_type const;
using NodePtrType = NodeType const *;
@ -48,12 +51,13 @@ namespace xo {
template <typename Key,
typename Value,
typename Reduce,
typename GcObjectInterface,
bool IsConst>
class IteratorBase {
public:
using RbUtil = RbTreeUtil<Key, Value, Reduce>;
using RbNode = Node<Key, Value, Reduce>;
using Traits = NodeTypeTraits<Key, Value, Reduce, IsConst>;
using RbUtil = RbTreeUtil<Key, Value, Reduce, GcObjectInterface>;
using RbNode = Node<Key, Value, Reduce, GcObjectInterface>;
using Traits = NodeTypeTraits<Key, Value, Reduce, GcObjectInterface, IsConst>;
using ReducedValue = typename Reduce::value_type;
using RbNativeNodeType = typename Traits::NativeNodeType;
using RbNodePtrType = typename Traits::NodePtrType;
@ -233,12 +237,17 @@ namespace xo {
*/
template <typename Key,
typename Value,
typename Reduce>
class Iterator : public IteratorBase<Key, Value, Reduce, false /*!IsConst*/> {
typename Reduce,
typename GcObjectInterface>
class Iterator : public IteratorBase<Key,
Value,
Reduce,
GcObjectInterface,
false /*!IsConst*/> {
public:
using iterator_concept = std::bidirectional_iterator_tag;
using RbIteratorBase = IteratorBase<Key, Value, Reduce, false /*!IsConst*/>;
using RbIteratorBase = IteratorBase<Key, Value, Reduce, GcObjectInterface, false /*!IsConst*/>;
using RbNode = typename RbIteratorBase::RbNode;
using RbUtil = typename RbIteratorBase::RbUtil;
using ReducedValue = typename Reduce::value_type;
@ -298,12 +307,13 @@ namespace xo {
*/
template <typename Key,
typename Value,
typename Reduce>
class ConstIterator : public IteratorBase<Key, Value, Reduce, true /*IsConst*/> {
typename Reduce,
typename GcObjectInterface>
class ConstIterator : public IteratorBase<Key, Value, Reduce, GcObjectInterface, true /*IsConst*/> {
public:
using iterator_concept = std::bidirectional_iterator_tag;
using RbIteratorBase = IteratorBase<Key, Value, Reduce, true /*IsConst*/>;
using RbIteratorBase = IteratorBase<Key, Value, Reduce, GcObjectInterface, true /*IsConst*/>;
using RbNode = typename RbIteratorBase::RbNode;
using RbUtil = typename RbIteratorBase::RbUtil;
using ReducedValue = typename Reduce::value_type;

View file

@ -6,14 +6,18 @@
#pragma once
#include "RbTypes.hpp"
#include "xo/reflect/Reflect.hpp"
#include "xo/indentlog/scope.hpp"
#include "xo/allocutil/IObject.hpp"
#include "xo/allocutil/gc_allocator_traits.hpp"
#include <cassert>
#include <utility>
namespace xo {
namespace tree {
namespace detail {
/** see xo/ordinaltree/rbtree/RbTreeUtil.hpp */
template <typename Key, typename Value, typename Reduce>
template <typename Key, typename Value, typename Reduce, typename GcObjectInterface>
class RbTreeUtil;
/* xo::tree::detail::Node
@ -25,19 +29,23 @@ namespace xo {
*/
template <typename Key,
typename Value,
typename Reduce>
class Node {
typename Reduce,
typename GcObjectInterface>
class Node : public GcObjectInterface {
public:
using ReducedValue = typename Reduce::value_type;
using ContentsType = std::pair<Key const, Value>;
using value_type = std::pair<Key const , Value>;
using Reflect = xo::reflect::Reflect;
using TaggedPtr = xo::reflect::TaggedPtr;
using IObject = xo::IObject;
public:
Node() = default;
Node(value_type const & kv_pair,
std::pair<ReducedValue, ReducedValue> const & r)
: color_(C_Red), size_(1), contents_{kv_pair}, reduced_(r) {}
Node(value_type && kv_pair,
Node(value_type && kv_pair,
std::pair<ReducedValue, ReducedValue> && r)
: color_(C_Red), size_(1),
contents_{std::move(kv_pair)},
@ -115,8 +123,7 @@ namespace xo {
static std::pair<ReducedValue,
ReducedValue> reduced_pair(Reduce r, Node const * x)
{
if(!x)
assert(false);
assert(x != nullptr);
ReducedValue r1 = r(reduce_aux(r, x->left_child()),
x->value());
@ -294,7 +301,7 @@ namespace xo {
xtag("r2", this->reduced2()));
} /*local_recalc_size*/
private:
private:
void assign_color(Color x) { this->color_ = x; }
void assign_size(size_t z) { this->size_ = z; }
@ -336,8 +343,24 @@ namespace xo {
}
} /*replace_child_reparent*/
friend class RbTreeUtil<Key, Value, Reduce>;
friend class xo::tree::RedBlackTree<Key, Value, Reduce>;
// ----- (possibly) inherited from GcObjectInterface -----
virtual TaggedPtr self_tp() const { return Reflect::make_tp(const_cast<Node*>(this)); }
virtual void display(std::ostream & os) const { os << "<Node>"; }
virtual std::size_t _shallow_size() const { return sizeof(*this); }
/* note: only relevant when GcObjectInterface is xo::IObject */
template <typename T = GcObjectInterface>
virtual IObject * _shallow_copy(gc::IAlloc * gc) const
-> std::enable_if_t<std::is_base_of_v<xo::IObject:
{
/* assert appropriate here. code will be present but dead when assert fails */
xo::Cpof cpof(gc, this);
return new (cpof) Node(*this);
}
friend class RbTreeUtil<Key, Value, Reduce, GcObjectInterface>;
template <typename Key1, typename Value1, typename Reduce1, typename Allocator>
friend class xo::tree::RedBlackTree;
private:
/* red | black */

View file

@ -18,10 +18,11 @@ namespace xo {
*/
template <typename Key,
typename Value,
typename Reduce>
typename Reduce,
typename GcObjectInterface>
class RbTreeUtil {
public:
using RbNode = Node<Key, Value, Reduce>;
using RbNode = Node<Key, Value, Reduce, GcObjectInterface>;
using ReducedValue = typename Reduce::value_type;
using value_type = std::pair<Key const, Value>;

View file

@ -2,7 +2,7 @@
# note: tests in this directory use Catch2-provided main
set(SELF_EXE utest.tree)
set(SELF_SOURCE_FILES tree_utest_main.cpp redblacktree.cpp bplustree.cpp)
set(SELF_SOURCE_FILES tree_utest_main.cpp redblacktree.cpp bplustree.cpp RedBlackTree-gc.test.cpp)
add_executable(${SELF_EXE} ${SELF_SOURCE_FILES})
xo_include_options2(${SELF_EXE})
@ -16,6 +16,7 @@ add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE})
# internal dependencies: refcnt, ...
xo_self_dependency(${SELF_EXE} xo_ordinaltree)
xo_dependency(${SELF_EXE} xo_alloc)
xo_dependency(${SELF_EXE} refcnt)
xo_dependency(${SELF_EXE} indentlog)
xo_dependency(${SELF_EXE} randomgen)

View file

@ -0,0 +1,104 @@
/** @file RedBlackTree-gc.test.cpp
**/
#include "random_tree_ops.hpp"
#include "xo/ordinaltree/RedBlackTree.hpp"
#include "xo/ordinaltree/rbtree/SumReduce.hpp"
#include "xo/alloc/GC.hpp"
#include <catch2/catch.hpp>
namespace xo {
using xo::gc::GC;
using xo::tree::RedBlackTree;
using xo::tree::SumReduce;
using utest::TreeUtil;
namespace ut {
namespace {
struct Testcase_RbTree {
Testcase_RbTree(std::size_t nz,
std::size_t tz,
std::size_t ngct,
std::size_t tgct) : nursery_z_{nz},
tenured_z_{tz},
incr_gc_threshold_{ngct},
full_gc_threshold_{tgct} {}
std::size_t nursery_z_;
std::size_t tenured_z_;
std::size_t incr_gc_threshold_;
std::size_t full_gc_threshold_;
};
std::vector<Testcase_RbTree>
s_testcase_v = {
Testcase_RbTree(1024, 4096, 512, 512),
};
}
TEST_CASE("rbtree-gc-1", "[gc][redblacktree]")
{
using RbTree = RedBlackTree<int,
double,
SumReduce<double>,
xo::gc::allocator<std::pair<const int, double>>>;
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) {
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"));
ok_flag = true; // unless contradicted below
up<GC> 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_ = debug_flag
}
);
xo::gc::allocator<RbTree::value_type> allocator(gc.get());
/* verify that we can construct a tree node */
RbTree::RbNode node;
#ifdef NOT_YET
RedBlackTree::node_allocator_traits::construct(allocator, &node,
{0, 0.0},
{0.0, 0.0});
#endif
#ifdef NOT_YET
RbTree rbtree(allocator, c_debug_flag);
/* insert [0..n-1] in random order **/
ok_flag &= TreeUtil<RbTree>::random_inserts(n, debug_flag, &rgen, &rbtree);
#endif
}
}
}
}
} /*namespace ut*/
} /*namesapce xo*/
/* end RedBlackTree-gc.test.cpp */