From 39a9dc725ced685a58eab9e834c6831fb9ae9f10 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Dec 2025 18:38:29 -0500 Subject: [PATCH] xo-ordinaltree: expand unittest + debug logging --- include/xo/allocutil/IAlloc.hpp | 32 ++++++-- include/xo/allocutil/IObject.hpp | 4 +- include/xo/allocutil/gc_allocator_traits.hpp | 86 +++++++++++++++----- 3 files changed, 92 insertions(+), 30 deletions(-) diff --git a/include/xo/allocutil/IAlloc.hpp b/include/xo/allocutil/IAlloc.hpp index 2c78b13..b38272a 100644 --- a/include/xo/allocutil/IAlloc.hpp +++ b/include/xo/allocutil/IAlloc.hpp @@ -143,8 +143,8 @@ namespace xo { /** check write barrier (if impl has write barrier) * given an object @p parent that contains object pointer @p lhs. **/ - virtual bool check_write_barrier(IObject * /*parent*/, - IObject ** /*lhs*/, + virtual bool check_write_barrier(const void * /*parent*/, + const void * const * /*lhs*/, bool /*may_throw*/) const { return true; }; /** write barrier for collector. perform assignment * @code @@ -169,6 +169,16 @@ namespace xo { } }; + /** for gc_allocator_traits, want an allocator pointer we can inherit from **/ + struct IAllocPtr { + public: + inline bool check_write_barrier(const void * parent, const void * const * lhs, bool may_throw) const { + return mm_->check_write_barrier(parent, lhs, may_throw); + } + + IAlloc * mm_ = nullptr; + }; + /** allocator wrapper (in the style of std::allocator) **/ template @@ -183,10 +193,11 @@ namespace xo { using difference_type = std::ptrdiff_t; using gc_object_interface = IAlloc::gc_object_interface; + using gc_interface = IAllocPtr; using has_incremental_gc_interface = IAlloc::has_incremental_gc_interface; /** rebind is for typed allocators. since IAlloc is untyped, - * we want degenerate version + * rebind is almost trivial **/ template struct rebind { @@ -194,16 +205,16 @@ namespace xo { }; public: - explicit allocator(IAlloc * mm) : mm_{mm} {} + explicit allocator(IAlloc * mm) : impl_{mm} {} allocator(const allocator &) = default; allocator & operator=(const allocator &) = default; template - allocator(const allocator & other) : mm_{other.mm_} {} + allocator(const allocator & other) : impl_{other.impl_.mm_} {} pointer allocate(size_type n) { - std::byte * raw = mm_->allocate(n * sizeof(T)); + std::byte * raw = impl_.mm_->allocate(n * sizeof(T)); return reinterpret_cast(raw); } @@ -211,7 +222,7 @@ namespace xo { void deallocate(pointer p, size_type n) { std::byte * raw = reinterpret_cast(p); - mm_->deallocate(raw, n * sizeof(T)); + impl_.mm_->deallocate(raw, n * sizeof(T)); } // optional construct, destroy (but allocator_traits provides defaults) @@ -221,11 +232,14 @@ namespace xo { **/ template bool operator==(const allocator & other) const noexcept { - return mm_ == other.mm_; + return impl_.mm_ == other.impl_.mm_; } + /** gc_interface=IAlloc **/ + operator gc_interface () const { return impl_; } + public: - IAlloc * mm_ = nullptr; + IAllocPtr impl_; }; } /*namespace gc*/ diff --git a/include/xo/allocutil/IObject.hpp b/include/xo/allocutil/IObject.hpp index 086d413..6cad929 100644 --- a/include/xo/allocutil/IObject.hpp +++ b/include/xo/allocutil/IObject.hpp @@ -29,6 +29,8 @@ namespace xo { /** GC write barrier: * assign value @p rhs to member @p *lhs of @p parent. * Identifiy and remember cross-generational pointers. + * + * Expect Allocator is xo::gc::allocator (see IAlloc.hpp) **/ template void _gc_assign_member(T ** lhs, @@ -37,7 +39,7 @@ namespace xo { { static_assert(std::is_convertible_v); - alloc.mm_->assign_member(this, reinterpret_cast(lhs), rhs); + alloc.impl_.mm_->assign_member(this, reinterpret_cast(lhs), rhs); } /** true iff this object represents a forwarding pointer. diff --git a/include/xo/allocutil/gc_allocator_traits.hpp b/include/xo/allocutil/gc_allocator_traits.hpp index 88ab33c..2056868 100644 --- a/include/xo/allocutil/gc_allocator_traits.hpp +++ b/include/xo/allocutil/gc_allocator_traits.hpp @@ -40,6 +40,19 @@ namespace xo { virtual std::size_t _forward_children(gc::IAlloc *) { assert(false); return 0; } }; + /** dummy GC interface. + * non-empty intersection with IAlloc + **/ + template + struct FallbackGcInterface { + template + FallbackGcInterface(Allocator & alloc) {} + + bool check_write_barrier(const void * parent, + const void * const * lhs, + bool may_throw) { return true; }; + }; + /** Extended version of * std::allocator_traits * Introduces additional i/face methods @@ -136,6 +149,8 @@ namespace xo { using super::allocate; using super::deallocate; + // ---------------------------------------------------------------- + // default: allocator A fallback to standard non-gc allocator behavior template struct has_incremental_gc_interface : std::false_type {}; @@ -148,6 +163,19 @@ namespace xo { struct has_incremental_gc_interface> : A::has_incremental_gc_interface {}; + /** true iff this allocator advertises itself as an incremental collector. + * Allocator will include: + * + * struct IAlloc { + * using has_incremental_gc_interface = std::true_type; + * }; + **/ + static inline constexpr + bool + has_incremental_gc_interface_v = has_incremental_gc_interface::value; + + // ---------------------------------------------------------------- + // default: allocate A fallback to standard non-GC allocator behavior template struct has_trivial_deallocate : std::false_type {}; @@ -160,6 +188,19 @@ namespace xo { struct has_trivial_deallocate> : A::has_trivial_deallocate {}; + /** true iff this allocator advertises trivial deallocate + * Allocate will include: + * + * struct IAlloc { + * using has_trivial_deallocate = std::true_type; + * }; + **/ + static inline constexpr + bool + has_trivial_deallocate_v = has_trivial_deallocate::value; + + // ---------------------------------------------------------------- + // default: empty object interface. // // classes that want to conditionally support GC @@ -187,27 +228,32 @@ namespace xo { // using object_interface_type = object_interface; - /** true iff this allocator advertises itself as an incremental collector. - * Allocator will include: - * - * struct IAlloc { - * using has_incremental_gc_interface = std::true_type; - * }; - **/ - static inline constexpr - bool - has_incremental_gc_interface_v = has_incremental_gc_interface::value; + // ---------------------------------------------------------------- + + // default: minimal garbage collector interface. + // + // Use in allocator-aware components that need conditionally + // to engage with GC functionality. + // For example in RedBlackTree::verify_ok() want to check + // cross-generational pointers. + // + // gc_interface gc + // - gc_interface(A & alloc) + // - gc.check_write_barrier(const object_interface_type * p, + // const object_interface_type * const * lhs, + // bool may_throw) + // + template + struct gc_interface : public FallbackGcInterface {}; + + // allocator opt-in by providing a gc_interface type + template + struct gc_interface> : public A::gc_interface {}; + + // interface for (narrow) GC interaction. + // Construct from allocator + using gc_interface_type = gc_interface; - /** true iff this allocator advertises trivial deallocate - * Allocate will include: - * - * struct IAlloc { - * using has_trivial_deallocate = std::true_type; - * }; - **/ - static inline constexpr - bool - has_trivial_deallocate_v = has_trivial_deallocate::value; }; } /*namespace gc*/ } /*namespace xo*/