From 3a840546fe72cda50158fb559720d366f64534f8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Dec 2025 14:22:41 -0500 Subject: [PATCH] xo-alloc xo-ordinaltree: GC option work in progress --- CMakeLists.txt | 2 +- xo-alloc/include/xo/alloc/Object.hpp | 2 +- xo-alloc/utest/Forwarding1.test.cpp | 4 +- xo-alloc/utest/IAlloc.test.cpp | 2 +- xo-allocutil/include/xo/allocutil/IAlloc.hpp | 27 +++++++++++ .../xo/allocutil/gc_allocator_traits.hpp | 46 +++++++++++++++---- xo-ordinaltree/CMakeLists.txt | 1 + .../cmake/xo_ordinaltreeConfig.cmake.in | 1 + .../include/xo/ordinaltree/RedBlackTree.hpp | 9 ++-- .../include/xo/ordinaltree/rbtree/Node.hpp | 3 ++ .../xo/ordinaltree/rbtree/RbTreeUtil.hpp | 4 -- xo-ordinaltree/utest/CMakeLists.txt | 1 + 12 files changed, 79 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 53c40f2e..2ee2db02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,7 @@ set(DOX_EXCLUDE_PATTERNS [=[ add_subdirectory(xo-cmake) add_subdirectory(xo-indentlog) +add_subdirectory(xo-allocutil) add_subdirectory(xo-refcnt) add_subdirectory(xo-subsys) add_subdirectory(xo-randomgen) @@ -90,7 +91,6 @@ add_subdirectory(xo-unit) add_subdirectory(xo-pyunit) add_subdirectory(xo-callback) # -add_subdirectory(xo-allocutil) add_subdirectory(xo-alloc) add_subdirectory(xo-object) # diff --git a/xo-alloc/include/xo/alloc/Object.hpp b/xo-alloc/include/xo/alloc/Object.hpp index 2468c8bc..2c11ea9e 100644 --- a/xo-alloc/include/xo/alloc/Object.hpp +++ b/xo-alloc/include/xo/alloc/Object.hpp @@ -64,7 +64,7 @@ namespace xo { * 3. return the location of the copy make in step 1. * * @p src. source object to be forwarded - * @p gc. garbage collector + * @p gc. allocator (poassibly garbage collector) */ static IObject * _forward(IObject * src, gc::IAlloc * gc); diff --git a/xo-alloc/utest/Forwarding1.test.cpp b/xo-alloc/utest/Forwarding1.test.cpp index 19d9bf1e..f0a81c14 100644 --- a/xo-alloc/utest/Forwarding1.test.cpp +++ b/xo-alloc/utest/Forwarding1.test.cpp @@ -23,7 +23,7 @@ namespace xo { gp member() const { return member_; } void assign_member(Object * x) { - Object::mm->assign_member(this, member_.ptr_address(), x); + Object::mm->assign_member(this, reinterpret_cast(member_.ptr_address()), x); } TaggedPtr self_tp() const final override { @@ -33,7 +33,7 @@ namespace xo { void display(std::ostream & os) const final override { os << data_; } virtual std::size_t _shallow_size() const final override { return sizeof(*this); } - virtual Object * _shallow_copy(gc::IAlloc * mm) const final override { return new (Cpof(mm, this)) DummyObject(*this); } + virtual IObject * _shallow_copy(gc::IAlloc * mm) const final override { return new (Cpof(mm, this)) DummyObject(*this); } virtual std::size_t _forward_children(gc::IAlloc * gc) final override { return _shallow_size(); } private: diff --git a/xo-alloc/utest/IAlloc.test.cpp b/xo-alloc/utest/IAlloc.test.cpp index b0214749..823791ab 100644 --- a/xo-alloc/utest/IAlloc.test.cpp +++ b/xo-alloc/utest/IAlloc.test.cpp @@ -3,7 +3,7 @@ * author: Roland Conybeare, Aug 2025 */ -#include "xo/alloc/IAlloc.hpp" +#include "xo/allocutil/IAlloc.hpp" #include namespace xo { diff --git a/xo-allocutil/include/xo/allocutil/IAlloc.hpp b/xo-allocutil/include/xo/allocutil/IAlloc.hpp index f2e6de31..5c18a2a7 100644 --- a/xo-allocutil/include/xo/allocutil/IAlloc.hpp +++ b/xo-allocutil/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/xo-allocutil/include/xo/allocutil/gc_allocator_traits.hpp b/xo-allocutil/include/xo/allocutil/gc_allocator_traits.hpp index b2fc4168..e183471b 100644 --- a/xo-allocutil/include/xo/allocutil/gc_allocator_traits.hpp +++ b/xo-allocutil/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*/ diff --git a/xo-ordinaltree/CMakeLists.txt b/xo-ordinaltree/CMakeLists.txt index 74ba5859..105d45d6 100644 --- a/xo-ordinaltree/CMakeLists.txt +++ b/xo-ordinaltree/CMakeLists.txt @@ -40,4 +40,5 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # NOTE: dependency set here must be kept consistent with ordinaltree/cmake/xo_ordinaltreeConfig.cmake.in # xo-ordinaltree is also header-only +xo_headeronly_dependency(${SELF_LIB} xo_allocutil) xo_headeronly_dependency(${SELF_LIB} randomgen) diff --git a/xo-ordinaltree/cmake/xo_ordinaltreeConfig.cmake.in b/xo-ordinaltree/cmake/xo_ordinaltreeConfig.cmake.in index 7e308d14..9884d371 100644 --- a/xo-ordinaltree/cmake/xo_ordinaltreeConfig.cmake.in +++ b/xo-ordinaltree/cmake/xo_ordinaltreeConfig.cmake.in @@ -2,5 +2,6 @@ include(CMakeFindDependencyMacro) find_dependency(randomgen) +find_dependency(xo_allocutil) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/xo-ordinaltree/include/xo/ordinaltree/RedBlackTree.hpp b/xo-ordinaltree/include/xo/ordinaltree/RedBlackTree.hpp index 1674c892..45313af2 100644 --- a/xo-ordinaltree/include/xo/ordinaltree/RedBlackTree.hpp +++ b/xo-ordinaltree/include/xo/ordinaltree/RedBlackTree.hpp @@ -112,10 +112,10 @@ namespace xo { using allocator_traits = std::allocator_traits; using ReducedValue = typename Reduce::value_type; - using RbTreeLhs = detail::RedBlackTreeLhs>; - using RbTreeConstLhs = detail::RedBlackTreeConstLhs>; using RbUtil = detail::RbTreeUtil; using RbNode = detail::Node; + using RbTreeLhs = detail::RedBlackTreeLhs; + using RbTreeConstLhs = detail::RedBlackTreeConstLhs; using node_type = RbNode; using node_allocator_type = typename std::allocator_traits::template rebind_alloc; @@ -442,13 +442,14 @@ namespace xo { } /*find_sum_glb*/ void clear() { - auto visitor_fn = [](RbNode const * x, uint32_t /*d*/) { + auto visitor_fn = [this](RbNode const * x, uint32_t /*d*/) { /* RbUtil.postorder_node_visitor() isn't expecting us to * alter node, but will not examine it after it's deleted */ RbNode * xx = const_cast(x); - delete xx; + node_allocator_traits::deallocate(node_alloc_, xx, 1); + // delete x }; RbUtil::postorder_node_visitor(this->root_, diff --git a/xo-ordinaltree/include/xo/ordinaltree/rbtree/Node.hpp b/xo-ordinaltree/include/xo/ordinaltree/rbtree/Node.hpp index 0cc1904d..acf55fec 100644 --- a/xo-ordinaltree/include/xo/ordinaltree/rbtree/Node.hpp +++ b/xo-ordinaltree/include/xo/ordinaltree/rbtree/Node.hpp @@ -6,6 +6,7 @@ #pragma once #include "RbTypes.hpp" +#include "xo/allocutil/gc_allocator_traits.hpp" #include namespace xo { @@ -62,11 +63,13 @@ namespace xo { } } /*make_leaf*/ +#ifdef OBSOLETE static Node * make_leaf(value_type && kv_pair, ReducedValue const & leaf_rv) { return new Node(kv_pair, std::pair(leaf_rv, leaf_rv)); } /*make_leaf*/ +#endif /* return #of key/vaue pairs in tree rooted at x. */ static size_t tree_size(Node *x) { diff --git a/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTreeUtil.hpp b/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTreeUtil.hpp index 15770bdc..a5c48762 100644 --- a/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTreeUtil.hpp +++ b/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTreeUtil.hpp @@ -779,7 +779,6 @@ namespace xo { * - f=false for existing node (k already in tree before this call) * - n=node containing key k */ - template static std::pair insert_aux(NodeAllocator & alloc, value_type const & kv_pair, @@ -885,7 +884,6 @@ namespace xo { * - N has no child nodes * - N->parent() != nullptr */ - template static void remove_black_leaf(NodeAllocator & alloc, RbNode *N, Reduce const & reduce_fn, @@ -1342,7 +1340,6 @@ namespace xo { * * return true if a node was removed; false otherwise. */ - template static bool erase_aux(NodeAllocator & alloc, Key const & k, Reduce const & reduce_fn, @@ -1556,7 +1553,6 @@ namespace xo { return true; } /*erase_aux*/ - template static void erase_1child_aux(NodeAllocator & alloc, RbNode * N, Reduce const & reduce_fn, diff --git a/xo-ordinaltree/utest/CMakeLists.txt b/xo-ordinaltree/utest/CMakeLists.txt index 7f888f48..a0985542 100644 --- a/xo-ordinaltree/utest/CMakeLists.txt +++ b/xo-ordinaltree/utest/CMakeLists.txt @@ -15,6 +15,7 @@ add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) # ---------------------------------------------------------------- # internal dependencies: refcnt, ... +xo_self_dependency(${SELF_EXE} xo_ordinaltree) xo_dependency(${SELF_EXE} refcnt) xo_dependency(${SELF_EXE} indentlog) xo_dependency(${SELF_EXE} randomgen)