From 7a2dc433bc0cae510c6aa395eff3a4aee78d6b20 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Dec 2025 01:20:49 -0500 Subject: [PATCH 01/17] xo-alloc + xo-allocutil: refactor to shrink dep surface area --- CMakeLists.txt | 43 ++++++ cmake/xo-bootstrap-macros.cmake | 14 ++ cmake/xo_allocutilConfig.cmake.in | 6 + include/xo/allocutil/IAlloc.hpp | 154 +++++++++++++++++++ include/xo/allocutil/IObject.hpp | 97 ++++++++++++ include/xo/allocutil/gc_allocator_traits.hpp | 75 +++++++++ include/xo/allocutil/gc_ptr.hpp | 77 ++++++++++ 7 files changed, 466 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_allocutilConfig.cmake.in create mode 100644 include/xo/allocutil/IAlloc.hpp create mode 100644 include/xo/allocutil/IObject.hpp create mode 100644 include/xo/allocutil/gc_allocator_traits.hpp create mode 100644 include/xo/allocutil/gc_ptr.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..83f0a59f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,43 @@ +# xo-allocutil/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_allocutil VERSION 0.1) +enable_language(CXX) + +# common XO macros (see github:Rconybea/xo-cmake) +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- +# output targets + +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# header-only library + +set(SELF_LIB xo_allocutil) +xo_add_headeronly_library(${SELF_LIB}) + +# ---------------------------------------------------------------- +# +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +# (note: ..Targets from xo_install_library2()) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# input dependencies + +# NOTE: dependency set here must be kept consistent with allocutil/cmake/xo_allocutilConfig.cmake.in + +# xo-allocutil is also header-only +#xo_headeronly_dependency(${SELF_LIB} randomgen) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..694d9b5c --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,14 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) diff --git a/cmake/xo_allocutilConfig.cmake.in b/cmake/xo_allocutilConfig.cmake.in new file mode 100644 index 00000000..00b2de9b --- /dev/null +++ b/cmake/xo_allocutilConfig.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +#find_dependency(randomgen) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/allocutil/IAlloc.hpp b/include/xo/allocutil/IAlloc.hpp new file mode 100644 index 00000000..f2e6de31 --- /dev/null +++ b/include/xo/allocutil/IAlloc.hpp @@ -0,0 +1,154 @@ +/** @file IAlloc.hpp + * + * @author: Roland Conybeare, Jul 2025 + **/ + +#pragma once + +#include +#include + +namespace xo { + template + using up = std::unique_ptr; + + class IObject; + + namespace gc { + /** @class IAllocator + * @brief Abstract API for allocation interface + * + * Garbage collector support methods: + * - checkpoint() + * - assign_member() + * - alloc_gc_copy() + * + * See class GC for copying incremental collector. + * See class ArenaAlloc for arena allocator + **/ + class IAlloc { + public: + virtual ~IAlloc() {} + + static inline std::uint32_t alloc_padding(std::size_t z) { + /* word size for alignment */ + constexpr uint32_t c_bpw = sizeof(std::uintptr_t); + + /* round up to multiple of c_bpw, but map 0 -> 0 + * (table assuming c_bpw==8) + * + * z%c_bpw dz + * ------------ + * 0 0 + * 1 7 + * 2 6 + * .. .. + * 7 1 + */ + std::uint32_t dz = (c_bpw - (z % c_bpw)) % c_bpw; + z += dz; + + return dz; + } + + static inline std::size_t with_padding(std::size_t z) { + return z + alloc_padding(z); + } + + /** optional name for this allocator; labelling for diagnostics **/ + virtual const std::string & name() const = 0; + /** allocator size in bytes (up to reserved limit) + * Includes unallocated mmeory + **/ + virtual std::size_t size() const = 0; + /** committed size in bytes **/ + virtual std::size_t committed() const = 0; + /** number of unallocated bytes available (up to soft limit) + * from this allocator + **/ + virtual std::size_t available() const = 0; + /** number of bytes allocated from this allocator **/ + virtual std::size_t allocated() const = 0; + /** true iff pointer x comes from this allocator **/ + virtual bool contains(const void * x) const = 0; + /** true iff object at address @p x was allocated by this allocator, + * and before checkpoint + **/ + virtual bool is_before_checkpoint(const void * x) const = 0; + /** number of bytes allocated before @ref checkpoint **/ + virtual std::size_t before_checkpoint() const = 0; + /** number of bytes allocated since @ref checkpoint **/ + virtual std::size_t after_checkpoint() const = 0; + /** @return true iff debug logging enabled **/ + virtual bool debug_flag() const = 0; + + /** remember allocator state. All currently-allocated addresses xo + * will satisfy is_before_checkpoint(x). Subsequent allocations x + * will fail is_before_checkpoint(x), until checkpoint superseded + * by @ref clear or another call to @ref checkpoint + **/ + virtual void checkpoint() = 0; + + /** allocate @p z bytes of memory. returns pointer to first address **/ + virtual std::byte * alloc(std::size_t z) = 0; + /** reset allocator to empty state. **/ + virtual void clear() = 0; + + // ----- GC-specific methods ----- + + /** true iff this allocator owns object at address @p src. + * Use to assist Object::_shallow_move + **/ + virtual bool check_owned(IObject * /*src*/) const { return false; } + /** true iff object at address @p src must move as part of + * in-progress collection phase + **/ + virtual bool check_move(IObject * /*src*/) const { return false; } + /** write barrier for collector. perform assignment + * @code + * *lhs = rhs + * @endcode + * plus additional book keeping if needed (e.g. in @ref GC) + * Default implementation just does the assignment. + **/ + virtual void assign_member(IObject * /*parent*/, + IObject ** lhs, + IObject * rhs) { *lhs = rhs; } + /** allocate @p z bytes for copy of object at @p src. + * Only used in @ref GC. Default implementation asserts and returns nullptr + **/ + virtual std::byte * alloc_gc_copy(std::size_t /*z*/, const void * /*src*/) { + // LCOV_EXCL_START + //assert(false); + return nullptr; + // LCOV_EXCL_STOP + } + }; + } /*namespace gc*/ + + class MMPtr { + public: + explicit MMPtr(gc::IAlloc * mm) : mm_{mm} {} + + gc::IAlloc * mm_ = nullptr; + }; +} /*namespace xo*/ + +inline void * operator new (std::size_t z, const xo::MMPtr & mmp) { + return mmp.mm_->alloc(z); +} + +//inline void operator delete (void * p, const MMPtr & mmp) { +// mmp.mm_->free(reinterpret_cast(p)); +//} + +inline void * operator new[] (std::size_t z, const xo::MMPtr & mmp) { + return mmp.mm_->alloc(z); +} + +//inline void operator delete[] (void * p, const MMPtr & mmp) { +// mmp.mm_->free(reinterpret_cast(p)); +//} + + +/* end IAlloc.hpp */ diff --git a/include/xo/allocutil/IObject.hpp b/include/xo/allocutil/IObject.hpp new file mode 100644 index 00000000..c4b15800 --- /dev/null +++ b/include/xo/allocutil/IObject.hpp @@ -0,0 +1,97 @@ +/** @file IObject.hpp + * + * @author Roland Conybeare, Nov 2025 + **/ + +#pragma once + +#include +#include + +namespace xo { + namespace gc { class IAlloc; } + + /** @class IObject + * @brief Base interface for GC interaction + * + * Classes that can be collected inherit this api + * indirectly via xo::Object (see xo/alloc/Object.hpp) + **/ + class IObject { + public: + /** true iff this object represents a forwarding pointer. + * Forwarding pointers are exclusively created by the garbage collector; + * forwarding pointers (and only forwarding pointers) return true here. + **/ + virtual bool _is_forwarded() const { return false; } + + /** offset for uncommon situation where pointer address is offset from object + * base address + **/ + virtual IObject * _offset_destination(IObject * src) const { return src; }; + + /** replace this object with a forwarding pointer referring to @p dest. + **/ + virtual void _forward_to(IObject * dest) = 0; + + /** if this object represents a forwarding pointer, return its new location. + * forwarding pointers belong to the garbage collector implementation. + * (if you have to ask -- no, your class is not a forwarding pointer) + * all other objects return nullptr here. + **/ + virtual IObject * _destination() { return nullptr; } + + /** return amount of storage (including padding) consumed by this object, + * excluding immediate Object-pointer children + **/ + virtual std::size_t _shallow_size() const = 0; + + /** if subject is allocated by GC: + * - create copy C in to-space + * - destination C will be nursery|tenured depending on location of this. + * else + * - return this to disengage from GC + * + * Require: @ref mm is an instance of @ref gc::GC + **/ + virtual IObject * _shallow_copy(gc::IAlloc * gc) const = 0; + + /** update child pointers that refer to forwarding pointers, + * replacing them with the correct destination. + * See @ref Object::deep_move + * + * this gray object, located in to-space. + * fwd1 forwarding objects. + * Located in from-space. Invalid at end of GC cycle. + * p1,p2 source pointers. + * D1,D2 already-forwarded objects. located in to-space. + * + * before: + * this fwd1 + * +----+ +-+ + * | p1 ----->|x|-------> D1 + * | | +-+ + * | | + * | p2 ----------------> D2 + * +----+ + * + * after: + * this + * +----+ + * | p1 ----------------> D1 + * | | + * | | + * | p2 ----------------> D2 + * +----+ + * + * this is now white + * + * @return shallow size of *this. Must exactly match the amount of memory in to-space + * allocated by @ref _shallow_move + * + **/ + virtual std::size_t _forward_children(gc::IAlloc * gc) = 0; + }; +} + +/* end IObject.hpp */ diff --git a/include/xo/allocutil/gc_allocator_traits.hpp b/include/xo/allocutil/gc_allocator_traits.hpp new file mode 100644 index 00000000..b2fc4168 --- /dev/null +++ b/include/xo/allocutil/gc_allocator_traits.hpp @@ -0,0 +1,75 @@ +/** @file gc_allocator_traits.hpp + * + * @author Roland Conybeare, Nov 2025 + **/ + +#pragma once + +#include +#include + +namespace xo { + namespace gc { + /** Extended version of + * std::allocator_traits + * Introduces additional i/face methods + * for garbage-collector-enabled allocators + * + * allocator A can identify itself as a copying collector: + * 1. provide A::object_interface + * A::object_interface = xo::Object + * 2. provide A::is_incremental_collector + * A::is_incremental_collector = std::true_type + * Collectible objects must: + * 2a. inherit A::object_interface + * 2b. implement A::object_interface::_shallow_size() + * 2c. implement A::object_interface::_shallow_copy(alloc) + * 2d. implement A::object_interface::_forward_children(alloc) + **/ + template + struct gc_allocator_traits : std::allocator_traits { + using super = std::allocator_traits; + using pointer = typename super::pointer; + using value_type = typename super::value_type; + + // default: allocator A fallback to standard non-gc allocator behavior + template + struct is_incremental_collector : std::false_type {}; + + // opt-in: A provides nested type 'is_incremental_collector': + // struct A { + // using is_incremental_collector = std::true_type; + // }; + template + struct is_incremental_collector> : + A::is_incremental_collector {}; + + // default: empty object interface. + // classes that want to conditionally support GC + // (e.g. see xo::tree::RedBlackTree, xo::tree::Node + // in xo-ordinal-tree) + // can inherit + // gc_allocator_traits::template object_interface + // + template + struct object_interface {}; + + // specialization when A provides gc_object_interface + template + struct object_interface> + : A::gc_object_interface {}; + + /** true iff this allocator advertises itself as an incremental collector + * allocator will include: + * + * struct GC { + * using is_incremental_collector = std::true_type; + * }; + **/ + static inline constexpr bool is_incremental_collector_v = is_incremental_collector::value; + + }; + } /*namespace gc*/ +} /*namespace xo*/ + +/* end gc_allocator_traits.hpp */ diff --git a/include/xo/allocutil/gc_ptr.hpp b/include/xo/allocutil/gc_ptr.hpp new file mode 100644 index 00000000..bb4225ae --- /dev/null +++ b/include/xo/allocutil/gc_ptr.hpp @@ -0,0 +1,77 @@ +/** @file gc_ptr.hpp + * + * @author Roland Conybeare, Nov 2025 + **/ + +#pragma once + +#include + +namespace xo { + template + class gc_ptr; + + template + using gp = gc_ptr; + + /** wrapper for a pointer to garbage-collector-eligible T. + * Application code will usually use the alias template gp + **/ + template + class gc_ptr { + public: + using element_type = T; + + public: + gc_ptr() = default; + gc_ptr(T * p) : ptr_{p} {} + gc_ptr(const gc_ptr & x) : ptr_{x.ptr_} {} + + /** create from gc_ptr to some related type @tparam S **/ + template + gc_ptr(const gc_ptr & x) : ptr_{x.ptr()} {} + + /** runtime downcast. shorthand for dynamic_cast **/ + template + static gc_ptr from(const gc_ptr & x) { return gc_ptr{dynamic_cast(x.ptr())}; } + + /** convenience for static asserts **/ + static constexpr bool is_gc_ptr = true; + /** see also: xo/refcnt/Refcounted.hpp **/ + static constexpr bool is_rc_ptr = false; + + static bool is_eq(gc_ptr x1, gc_ptr x2) { + std::uintptr_t u1 = reinterpret_cast(x1.ptr()); + std::uintptr_t u2 = reinterpret_cast(x2.ptr()); + + // multiple inheritance shenanigans. + // (allow interface pointers separated by one pointer) + + if (u1 >= u2) + return (u1 <= u2 + sizeof(std::uintptr_t)); + else + return (u2 <= u1 + sizeof(std::uintptr_t)); + } + + /** (for consistency's sake) **/ + T * get() const { return ptr_; } + + T * ptr() const { return ptr_; } + T ** ptr_address() { return &ptr_; } + + bool is_null() const { return ptr_ == nullptr; } + void make_null() { ptr_ = nullptr; } + + void assign_ptr(T * x) { ptr_ = x; } + + gc_ptr & operator=(const gc_ptr & x) { ptr_ = x.ptr(); return *this; } + + T * operator->() const { return ptr_; } + T & operator*() const { return *ptr_; } + + private: + T * ptr_ = nullptr; + }; +} /*namespace xo*/ + +/* end gc_ptr.hpp */ From 74d106c43e71946da533b9259032cce1f5f1eb12 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Dec 2025 14:22:41 -0500 Subject: [PATCH 02/17] xo-alloc xo-ordinaltree: GC option work in progress --- include/xo/allocutil/IAlloc.hpp | 27 ++++++++++++ include/xo/allocutil/gc_allocator_traits.hpp | 46 +++++++++++++++----- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/include/xo/allocutil/IAlloc.hpp b/include/xo/allocutil/IAlloc.hpp index f2e6de31..5c18a2a7 100644 --- a/include/xo/allocutil/IAlloc.hpp +++ b/include/xo/allocutil/IAlloc.hpp @@ -5,6 +5,7 @@ #pragma once +#include "gc_allocator_traits.hpp" #include #include @@ -25,8 +26,17 @@ namespace xo { * * See class GC for copying incremental collector. * See class ArenaAlloc for arena allocator + * + * In either case deallocation is trivial **/ class IAlloc { + public: + using pointer_type = std::byte *; + using size_type = std::size_t; + /** traits: see gc_allocator_traits **/ + using gc_object_interface = xo::IObject; + using has_incremental_gc_interface = std::true_type; + public: virtual ~IAlloc() {} @@ -55,6 +65,23 @@ namespace xo { return z + alloc_padding(z); } + // ----- std::allocator interface ----- + + std::byte * allocate(std::size_t n) { + return this->alloc(n); + } + + /** std::allocator locality hint. Not used for now **/ + std::byte * allocate(std::size_t n, const void * hint) { + (void)hint; + return this->alloc(n); + } + + /** deallocation trivial for IAlloc **/ + void deallocate(std::byte *, std::size_t) {} + + // ----- IAlloc methods ----- + /** optional name for this allocator; labelling for diagnostics **/ virtual const std::string & name() const = 0; /** allocator size in bytes (up to reserved limit) diff --git a/include/xo/allocutil/gc_allocator_traits.hpp b/include/xo/allocutil/gc_allocator_traits.hpp index b2fc4168..e183471b 100644 --- a/include/xo/allocutil/gc_allocator_traits.hpp +++ b/include/xo/allocutil/gc_allocator_traits.hpp @@ -16,15 +16,41 @@ namespace xo { * for garbage-collector-enabled allocators * * allocator A can identify itself as a copying collector: + * * 1. provide A::object_interface - * A::object_interface = xo::Object - * 2. provide A::is_incremental_collector - * A::is_incremental_collector = std::true_type - * Collectible objects must: + * per-object header interface: tells garbage collector + * how to navigate object graph. + * A::gc_object_interface = xo::IObject + * contains virtual methods; classes that can be garbage + * collected should inherit this interface + * + * 2. provide A::has_incremental_gc_interface + * A::has_incremental_gc_interface = std::true_type + * This doesn't imply A is a garbage-collecting allocator; + * it just implies that it supports a collection api. + * - xo::gc::ArenaAlloc has a collection API, but does not + * provide garbage collection + * - xo::gc::GC has a collection API and also provides + * garbage collection + * + * GC-allocated objects must: * 2a. inherit A::object_interface * 2b. implement A::object_interface::_shallow_size() * 2c. implement A::object_interface::_shallow_copy(alloc) * 2d. implement A::object_interface::_forward_children(alloc) + * in multiple inheritance scenarios + * 2e. implement A::object_interface::_offset_destination(src) + * + * Design Notes: + * - virtual-method choice requires vtable pointer per object; + * but zero *marginal* space cost for types that would have + * a vtable pointer anyway. + * - can still handle non-vtable objects, by providing a + * object-interface-inheriting wrapper. + * - less-intrusive (though less space-efficient) alternative + * would be to use a type-registration system; + * then GC hooks could be setup independently of a subject type. + * (watch out for pimpl implementations though!) **/ template struct gc_allocator_traits : std::allocator_traits { @@ -34,15 +60,15 @@ namespace xo { // default: allocator A fallback to standard non-gc allocator behavior template - struct is_incremental_collector : std::false_type {}; + struct has_incremental_gc_interface : std::false_type {}; - // opt-in: A provides nested type 'is_incremental_collector': + // opt-in: A provides nested type 'has_incremental_collector_interface': // struct A { // using is_incremental_collector = std::true_type; // }; template - struct is_incremental_collector> : - A::is_incremental_collector {}; + struct has_incremental_gc_interface> : + A::has_incremental_gc_interface {}; // default: empty object interface. // classes that want to conditionally support GC @@ -63,10 +89,10 @@ namespace xo { * allocator will include: * * struct GC { - * using is_incremental_collector = std::true_type; + * using has_incremental_gc_interface = std::true_type; * }; **/ - static inline constexpr bool is_incremental_collector_v = is_incremental_collector::value; + static inline constexpr bool has_incremental_gc_interface_v = has_incremental_gc_interface::value; }; } /*namespace gc*/ From b1562b1ee6d0c6af74b1832436e73760dc6fa38a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Dec 2025 22:25:41 -0500 Subject: [PATCH 03/17] xo-ordinaltree: GC test [wip] --- include/xo/allocutil/IAlloc.hpp | 70 +++++++++++++++++++- include/xo/allocutil/IObject.hpp | 14 ++++ include/xo/allocutil/gc_allocator_traits.hpp | 4 ++ 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/include/xo/allocutil/IAlloc.hpp b/include/xo/allocutil/IAlloc.hpp index 5c18a2a7..4f9097f7 100644 --- a/include/xo/allocutil/IAlloc.hpp +++ b/include/xo/allocutil/IAlloc.hpp @@ -14,6 +14,7 @@ namespace xo { using up = std::unique_ptr; 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 **/ - using gc_object_interface = xo::IObject; + /** traits: see gc_allocator_traits + * + * 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 + 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 + struct rebind { + using other = allocator; + }; + + public: + explicit allocator(IAlloc * mm) : mm_{mm} {} + + allocator(const allocator &) = default; + allocator & operator=(const allocator &) = default; + + template + allocator(const allocator & other) : mm_{other.mm_} {} + + pointer allocate(size_type n) { + std::byte * raw = mm_->allocate(n * sizeof(T)); + + return reinterpret_cast(raw); + } + + void deallocate(pointer p, size_type n) { + std::byte * raw = reinterpret_cast(p); + + mm_->deallocate(raw, n * sizeof(T)); + } + + // optional construct, destroy (but allocator_traits provides defaults) + + /** required! otherwise allocator, allocator with the same IAlloc* + * would be considered to own disjoin memory addresses + **/ + template + bool operator==(const allocator & other) const noexcept { + return mm_ == other.mm_; + } + + public: + IAlloc * mm_ = nullptr; + }; + } /*namespace gc*/ class MMPtr { diff --git a/include/xo/allocutil/IObject.hpp b/include/xo/allocutil/IObject.hpp index c4b15800..87c60130 100644 --- a/include/xo/allocutil/IObject.hpp +++ b/include/xo/allocutil/IObject.hpp @@ -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 */ diff --git a/include/xo/allocutil/gc_allocator_traits.hpp b/include/xo/allocutil/gc_allocator_traits.hpp index e183471b..dd1a6322 100644 --- a/include/xo/allocutil/gc_allocator_traits.hpp +++ b/include/xo/allocutil/gc_allocator_traits.hpp @@ -57,6 +57,10 @@ namespace xo { using super = std::allocator_traits; 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 From 62024de44fcb9070c07f2944214afd21d3b9ba0c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Dec 2025 10:37:07 -0500 Subject: [PATCH 04/17] xo-alloc / xo-ordinaltree: work on dual-alloc-policy trees --- include/xo/allocutil/IAlloc.hpp | 6 ++++-- include/xo/allocutil/IObject.hpp | 3 +++ include/xo/allocutil/gc_allocator_traits.hpp | 13 ++++++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/include/xo/allocutil/IAlloc.hpp b/include/xo/allocutil/IAlloc.hpp index 4f9097f7..a322e1db 100644 --- a/include/xo/allocutil/IAlloc.hpp +++ b/include/xo/allocutil/IAlloc.hpp @@ -76,7 +76,7 @@ namespace xo { std::byte * allocate(std::size_t n) { return this->alloc(n); } - + /** std::allocator locality hint. Not used for now **/ std::byte * allocate(std::size_t n, const void * hint) { (void)hint; @@ -147,6 +147,8 @@ namespace xo { virtual void assign_member(IObject * /*parent*/, IObject ** lhs, IObject * rhs) { *lhs = rhs; } + /** if GC: evacuate @p *lhs and replace with forwarding pointer **/ + virtual void forward_inplace(IObject ** lhs) { (void)lhs; } /** allocate @p z bytes for copy of object at @p src. * Only used in @ref GC. Default implementation asserts and returns nullptr **/ @@ -170,7 +172,7 @@ namespace xo { 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; diff --git a/include/xo/allocutil/IObject.hpp b/include/xo/allocutil/IObject.hpp index 87c60130..21048884 100644 --- a/include/xo/allocutil/IObject.hpp +++ b/include/xo/allocutil/IObject.hpp @@ -19,6 +19,9 @@ namespace xo { **/ class IObject { public: + /** impl inheriting this class must provide gc hooks **/ + static constexpr bool _requires_gc_hooks = true; + /** true iff this object represents a forwarding pointer. * Forwarding pointers are exclusively created by the garbage collector; * forwarding pointers (and only forwarding pointers) return true here. diff --git a/include/xo/allocutil/gc_allocator_traits.hpp b/include/xo/allocutil/gc_allocator_traits.hpp index dd1a6322..38e7f384 100644 --- a/include/xo/allocutil/gc_allocator_traits.hpp +++ b/include/xo/allocutil/gc_allocator_traits.hpp @@ -32,7 +32,7 @@ namespace xo { * provide garbage collection * - xo::gc::GC has a collection API and also provides * garbage collection - * + * * GC-allocated objects must: * 2a. inherit A::object_interface * 2b. implement A::object_interface::_shallow_size() @@ -40,7 +40,7 @@ namespace xo { * 2d. implement A::object_interface::_forward_children(alloc) * in multiple inheritance scenarios * 2e. implement A::object_interface::_offset_destination(src) - * + * * Design Notes: * - virtual-method choice requires vtable pointer per object; * but zero *marginal* space cost for types that would have @@ -82,12 +82,16 @@ namespace xo { // gc_allocator_traits::template object_interface // template - struct object_interface {}; + struct object_interface { + /** see also IObject::_requires_gc_hooks **/ + static constexpr bool _requires_gc_hooks = false; + }; // specialization when A provides gc_object_interface template struct object_interface> - : A::gc_object_interface {}; + : public A::gc_object_interface { + }; /** true iff this allocator advertises itself as an incremental collector * allocator will include: @@ -97,7 +101,6 @@ namespace xo { * }; **/ static inline constexpr bool has_incremental_gc_interface_v = has_incremental_gc_interface::value; - }; } /*namespace gc*/ } /*namespace xo*/ From fe7edbe19268837047ef9e1842f7e9593ae9dd1c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Dec 2025 17:07:19 -0500 Subject: [PATCH 05/17] xo-alloc: UT for allocator interation + misc improvements --- include/xo/allocutil/IAlloc.hpp | 6 ++- include/xo/allocutil/ObjectVisitor.hpp | 53 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 include/xo/allocutil/ObjectVisitor.hpp diff --git a/include/xo/allocutil/IAlloc.hpp b/include/xo/allocutil/IAlloc.hpp index a322e1db..b126d8a3 100644 --- a/include/xo/allocutil/IAlloc.hpp +++ b/include/xo/allocutil/IAlloc.hpp @@ -46,9 +46,11 @@ namespace xo { public: virtual ~IAlloc() {} + /** word size for alignment **/ + static constexpr uint32_t c_alloc_alignment = sizeof(std::uintptr_t); + static inline std::uint32_t alloc_padding(std::size_t z) { - /* word size for alignment */ - constexpr uint32_t c_bpw = sizeof(std::uintptr_t); + constexpr uint32_t c_bpw = c_alloc_alignment; /* round up to multiple of c_bpw, but map 0 -> 0 * (table assuming c_bpw==8) diff --git a/include/xo/allocutil/ObjectVisitor.hpp b/include/xo/allocutil/ObjectVisitor.hpp new file mode 100644 index 00000000..1719ef70 --- /dev/null +++ b/include/xo/allocutil/ObjectVisitor.hpp @@ -0,0 +1,53 @@ +/** @file ObjectVisitor.hpp + * + **/ + +#include "IAlloc.hpp" +#include + +namespace xo { + namespace gc { + /** @class ObjectVisitor + * @brief visit IObject* members of a T instance + * + * Garbage collector relies on being able to navigate to + * an IObject-instance to find+update embedded pointers to + * other IObjects. + * + * An IObject implemnetation must override + * IObject::_forward_children(IAlloc * gc) + * and call + * gc->forward_inplace(&child_) + * for each such child pointer. + * + * For non-template classes this is straightforward + * See for example + * xo::obj::List in object/List.hpp + * + * For a template class Foo that contains T-instances, + * need to handle case where T contains IObject pointers. + * See for example the + * xo::tree::RedBlackTree in ordinaltree/RedBlackTree.hpp + * + * Use ObjectVisitor::forward_children() to pick up + * navigation code for such template arguments. + **/ + template + class ObjectVisitor { + //void forward_children(T & target, IAlloc * gc) { (void)target; (void)gc; } + }; + +#define XO_TRIVIAL_OBJECT_VISITOR(TYPE) \ + template <> \ + class ObjectVisitor { \ + public: \ + static void forward_children(TYPE &, IAlloc *) {} \ + } + + XO_TRIVIAL_OBJECT_VISITOR(int32_t); + XO_TRIVIAL_OBJECT_VISITOR(double); + + } /*namespace gc*/ +} /*namespace xo*/ + +/* ObjectVisitor.hpp */ From 92d22defe2a5f36b4a3eb20feb82fd3091aa93fb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Dec 2025 15:36:59 -0500 Subject: [PATCH 06/17] xo-alloc / xo-ordinaltree: + concepts + allocator-aware --- include/xo/allocutil/IObject.hpp | 2 ++ include/xo/allocutil/ObjectVisitor.hpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/xo/allocutil/IObject.hpp b/include/xo/allocutil/IObject.hpp index 21048884..c6f9611d 100644 --- a/include/xo/allocutil/IObject.hpp +++ b/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/include/xo/allocutil/ObjectVisitor.hpp b/include/xo/allocutil/ObjectVisitor.hpp index 1719ef70..276df554 100644 --- a/include/xo/allocutil/ObjectVisitor.hpp +++ b/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) \ From fa5c4ad1b63aa0489f086ff9ed41384ddafa89f5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Dec 2025 17:12:51 -0500 Subject: [PATCH 07/17] bugfix: missing #pragma once --- include/xo/allocutil/ObjectVisitor.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/xo/allocutil/ObjectVisitor.hpp b/include/xo/allocutil/ObjectVisitor.hpp index 276df554..08f01d2c 100644 --- a/include/xo/allocutil/ObjectVisitor.hpp +++ b/include/xo/allocutil/ObjectVisitor.hpp @@ -2,6 +2,8 @@ * **/ +#pragma once + #include "IAlloc.hpp" #include From b8c41b9317c0149005ce6c8dc80bceb00bb1f545 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Dec 2025 21:27:42 -0500 Subject: [PATCH 08/17] xo-allocutil: streamlet: object_interface_type --- include/xo/allocutil/gc_allocator_traits.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/xo/allocutil/gc_allocator_traits.hpp b/include/xo/allocutil/gc_allocator_traits.hpp index 38e7f384..30cbb4f4 100644 --- a/include/xo/allocutil/gc_allocator_traits.hpp +++ b/include/xo/allocutil/gc_allocator_traits.hpp @@ -93,6 +93,14 @@ namespace xo { : public A::gc_object_interface { }; + // classes that want to conditionally support GC + // (e.g. see xo::tree::RedBlackTree, xo::tree::Node + // in xo-ordinal-tree) + // can inherit + // gc_allocator_traits::object_interface_type + // + using object_interface_type = object_interface; + /** true iff this allocator advertises itself as an incremental collector * allocator will include: * From e65cee6968ceb376ff976404235d16557cda1576 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 4 Dec 2025 17:32:29 -0500 Subject: [PATCH 09/17] xo-alloc - compile-time property for trivial deallocate --- include/xo/allocutil/IAlloc.hpp | 1 + include/xo/allocutil/gc_allocator_traits.hpp | 43 +++++++++++++++++--- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/include/xo/allocutil/IAlloc.hpp b/include/xo/allocutil/IAlloc.hpp index b126d8a3..703e968c 100644 --- a/include/xo/allocutil/IAlloc.hpp +++ b/include/xo/allocutil/IAlloc.hpp @@ -42,6 +42,7 @@ namespace xo { **/ using gc_object_interface = xo::Object; using has_incremental_gc_interface = std::true_type; + using has_trivial_deallocate = std::true_type; public: virtual ~IAlloc() {} diff --git a/include/xo/allocutil/gc_allocator_traits.hpp b/include/xo/allocutil/gc_allocator_traits.hpp index 30cbb4f4..80184b0c 100644 --- a/include/xo/allocutil/gc_allocator_traits.hpp +++ b/include/xo/allocutil/gc_allocator_traits.hpp @@ -74,7 +74,20 @@ namespace xo { struct has_incremental_gc_interface> : A::has_incremental_gc_interface {}; + // default: allocate A fallback to standard non-GC allocator behavior + template + struct has_trivial_deallocate : std::false_type {}; + + // opt-in: A provides nested type 'has_trivial_deallocate': + // struct A { + // using has_trivial_deallocate = std::true_type; + // }; + template + struct has_trivial_deallocate> : + A::has_trivial_deallocate {}; + // default: empty object interface. + // // classes that want to conditionally support GC // (e.g. see xo::tree::RedBlackTree, xo::tree::Node // in xo-ordinal-tree) @@ -85,13 +98,24 @@ namespace xo { struct object_interface { /** see also IObject::_requires_gc_hooks **/ static constexpr bool _requires_gc_hooks = false; + /** see also IObject::_requires_write_barrier **/ + static constexpr bool _requires_write_barrier = false; + + /** see also IObject::_gc_assign_member **/ + template + void _gc_assign_member(T ** lhs, + T * rhs, + A & alloc) + { + *lhs = rhs; + } + }; // specialization when A provides gc_object_interface template struct object_interface> - : public A::gc_object_interface { - }; + : public A::gc_object_interface {}; // classes that want to conditionally support GC // (e.g. see xo::tree::RedBlackTree, xo::tree::Node @@ -101,14 +125,23 @@ namespace xo { // using object_interface_type = object_interface; - /** true iff this allocator advertises itself as an incremental collector - * allocator will include: + /** true iff this allocator advertises itself as an incremental collector. + * Allocator will include: * - * struct GC { + * struct IAlloc { * using has_incremental_gc_interface = std::true_type; * }; **/ static inline constexpr bool has_incremental_gc_interface_v = has_incremental_gc_interface::value; + + /** 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*/ From 37d08b8c67cc3218653305b64bbbcd3eeb63ae24 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 4 Dec 2025 17:33:40 -0500 Subject: [PATCH 10/17] xo-ordinaltree: add _gc_assign_member feature to gc-aware allocs --- include/xo/allocutil/IObject.hpp | 18 +++++++ include/xo/allocutil/gc_allocator_traits.hpp | 53 ++++++++++++++++++-- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/include/xo/allocutil/IObject.hpp b/include/xo/allocutil/IObject.hpp index c6f9611d..086d4135 100644 --- a/include/xo/allocutil/IObject.hpp +++ b/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/include/xo/allocutil/gc_allocator_traits.hpp b/include/xo/allocutil/gc_allocator_traits.hpp index 80184b0c..6ddda37b 100644 --- a/include/xo/allocutil/gc_allocator_traits.hpp +++ b/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 From 0a2dd316ee8ab97b5dfe951d271808cc4fb65a03 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 4 Dec 2025 21:31:55 -0500 Subject: [PATCH 11/17] xo-alloc/xo-ordinaltree: refactor rbtree Node alloc progress toward careful gc-aware assignment --- include/xo/allocutil/ObjectVisitor.hpp | 3 +- include/xo/allocutil/gc_allocator_traits.hpp | 61 +++++++++++++------- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/include/xo/allocutil/ObjectVisitor.hpp b/include/xo/allocutil/ObjectVisitor.hpp index 08f01d2c..df336876 100644 --- a/include/xo/allocutil/ObjectVisitor.hpp +++ b/include/xo/allocutil/ObjectVisitor.hpp @@ -36,7 +36,8 @@ 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/include/xo/allocutil/gc_allocator_traits.hpp b/include/xo/allocutil/gc_allocator_traits.hpp index 6ddda37b..88ab33ca 100644 --- a/include/xo/allocutil/gc_allocator_traits.hpp +++ b/include/xo/allocutil/gc_allocator_traits.hpp @@ -10,7 +10,36 @@ #include namespace xo { + class IObject; + namespace gc { + class IAlloc; + + /** object interface for allocators A that don't provide A::gc_object_interface. + * See gc_allocator_traits + **/ + struct FallbackObjectInterface { + /** see also IObject::_requires_gc_hooks **/ + static constexpr bool _requires_gc_hooks = false; + /** see also IObject::_requires_write_barrier **/ + static constexpr bool _requires_write_barrier = false; + + /** see also IObject::_gc_assign_member **/ + template + void _gc_assign_member(T ** lhs, + T * rhs, + AA & alloc) { + (void)alloc; + *lhs = rhs; + } + + virtual void display(std::ostream &) const {} + virtual bool _is_forwarded() const { return false; } + virtual std::size_t _shallow_size() const { assert(false); return 0; } + virtual IObject * _shallow_copy(gc::IAlloc *) const { assert(false); return nullptr; } + virtual std::size_t _forward_children(gc::IAlloc *) { assert(false); return 0; } + }; + /** Extended version of * std::allocator_traits * Introduces additional i/face methods @@ -140,26 +169,12 @@ namespace xo { // gc_allocator_traits::template object_interface // template - struct object_interface { - /** see also IObject::_requires_gc_hooks **/ - static constexpr bool _requires_gc_hooks = false; - /** see also IObject::_requires_write_barrier **/ - static constexpr bool _requires_write_barrier = false; + struct object_interface : public FallbackObjectInterface {}; - /** see also IObject::_gc_assign_member **/ - template - void _gc_assign_member(T ** lhs, - T * rhs, - A & alloc) - { - *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 + // specialization when an allocator A + // (which will actuallly be Allocator via SFINAE) + // provides gc_object_interface + // template struct object_interface> : public A::gc_object_interface {}; @@ -179,7 +194,9 @@ namespace xo { * using has_incremental_gc_interface = std::true_type; * }; **/ - static inline constexpr bool has_incremental_gc_interface_v = has_incremental_gc_interface::value; + static inline constexpr + bool + has_incremental_gc_interface_v = has_incremental_gc_interface::value; /** true iff this allocator advertises trivial deallocate * Allocate will include: @@ -188,7 +205,9 @@ namespace xo { * using has_trivial_deallocate = std::true_type; * }; **/ - static inline constexpr bool has_trivial_deallocate_v = has_trivial_deallocate::value; + static inline constexpr + bool + has_trivial_deallocate_v = has_trivial_deallocate::value; }; } /*namespace gc*/ } /*namespace xo*/ From b4d12edc003aeb4deb46fc7a35c3a07754df09ab Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Dec 2025 09:59:47 -0500 Subject: [PATCH 12/17] xo-ordinaltree: notify gc when root changes in rbtree.erase --- include/xo/allocutil/IAlloc.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/xo/allocutil/IAlloc.hpp b/include/xo/allocutil/IAlloc.hpp index 703e968c..2c78b131 100644 --- a/include/xo/allocutil/IAlloc.hpp +++ b/include/xo/allocutil/IAlloc.hpp @@ -140,6 +140,12 @@ namespace xo { * in-progress collection phase **/ virtual bool check_move(IObject * /*src*/) const { return false; } + /** 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*/, + bool /*may_throw*/) const { return true; }; /** write barrier for collector. perform assignment * @code * *lhs = rhs From 39a9dc725ced685a58eab9e834c6831fb9ae9f10 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Dec 2025 18:38:29 -0500 Subject: [PATCH 13/17] 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 2c78b131..b38272a4 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 086d4135..6cad9295 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 88ab33ca..20568689 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*/ From 89ce7f796227c215b839f8d54db0e4b9dda9ef2e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Dec 2025 19:54:00 -0500 Subject: [PATCH 14/17] xo-ordinaltree: start work on gc-aware Key,Value in rbtree --- include/xo/allocutil/IObject.hpp | 1 + include/xo/allocutil/ObjectVisitor.hpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/include/xo/allocutil/IObject.hpp b/include/xo/allocutil/IObject.hpp index 6cad9295..932966df 100644 --- a/include/xo/allocutil/IObject.hpp +++ b/include/xo/allocutil/IObject.hpp @@ -7,6 +7,7 @@ #include #include +#include namespace xo { namespace gc { class IAlloc; } diff --git a/include/xo/allocutil/ObjectVisitor.hpp b/include/xo/allocutil/ObjectVisitor.hpp index df336876..420d44cc 100644 --- a/include/xo/allocutil/ObjectVisitor.hpp +++ b/include/xo/allocutil/ObjectVisitor.hpp @@ -36,8 +36,9 @@ namespace xo { **/ template class ObjectVisitor { - void forward_children(T & target, - IAlloc * gc) { (void)target; (void)gc; } +// public: +// void forward_children(T & target, +// IAlloc * gc) { (void)target; (void)gc; } }; #define XO_TRIVIAL_OBJECT_VISITOR(TYPE) \ From 6c334d91d7db3ba186e023917788fdff815ae32c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Dec 2025 22:58:36 -0500 Subject: [PATCH 15/17] xo-ordinaltree: adj to drop RedBlackTree as friend of Node --- include/xo/allocutil/gc_allocator_traits.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/xo/allocutil/gc_allocator_traits.hpp b/include/xo/allocutil/gc_allocator_traits.hpp index 20568689..7e27d673 100644 --- a/include/xo/allocutil/gc_allocator_traits.hpp +++ b/include/xo/allocutil/gc_allocator_traits.hpp @@ -46,11 +46,11 @@ namespace xo { template struct FallbackGcInterface { template - FallbackGcInterface(Allocator & alloc) {} + FallbackGcInterface(Allocator & /*alloc*/) {} - bool check_write_barrier(const void * parent, - const void * const * lhs, - bool may_throw) { return true; }; + bool check_write_barrier(const void * /*parent*/, + const void * const * /*lhs*/, + bool /*may_throw*/) { return true; }; }; /** Extended version of From 3f9fac65a40fff923a42ce196cee260de585aac0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 6 Dec 2025 14:16:34 -0500 Subject: [PATCH 16/17] xo-object: spaceship operator for gp + gp --- include/xo/allocutil/gc_ptr.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/xo/allocutil/gc_ptr.hpp b/include/xo/allocutil/gc_ptr.hpp index bb4225ae..a7d09743 100644 --- a/include/xo/allocutil/gc_ptr.hpp +++ b/include/xo/allocutil/gc_ptr.hpp @@ -69,6 +69,18 @@ namespace xo { T * operator->() const { return ptr_; } T & operator*() const { return *ptr_; } + auto operator<=>(gc_ptr other) const + requires std::three_way_comparable + { + return *ptr_ <=> *other.ptr_; + } + + bool operator==(gc_ptr other) const + requires std::equality_comparable + { + return *ptr_ == *other.ptr_; + } + private: T * ptr_ = nullptr; }; From 52823fd3fc3a145cee9b676c321c4786d3fdf7c9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 27 Feb 2026 19:38:53 +1100 Subject: [PATCH 17/17] xo-cmake: setup to make share target available via cmake install --- cmake/xo_allocutilConfig.cmake.in | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/xo_allocutilConfig.cmake.in b/cmake/xo_allocutilConfig.cmake.in index 00b2de9b..a1cc0fc9 100644 --- a/cmake/xo_allocutilConfig.cmake.in +++ b/cmake/xo_allocutilConfig.cmake.in @@ -3,4 +3,5 @@ include(CMakeFindDependencyMacro) #find_dependency(randomgen) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Share.cmake") check_required_components("@PROJECT_NAME@")