xo-alloc xo-ordinaltree: GC option work in progress

This commit is contained in:
Roland Conybeare 2025-12-01 14:22:41 -05:00
commit 3a840546fe
12 changed files with 79 additions and 23 deletions

View file

@ -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)
#

View file

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

View file

@ -23,7 +23,7 @@ namespace xo {
gp<Object> 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<IObject**>(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:

View file

@ -3,7 +3,7 @@
* author: Roland Conybeare, Aug 2025
*/
#include "xo/alloc/IAlloc.hpp"
#include "xo/allocutil/IAlloc.hpp"
#include <catch2/catch.hpp>
namespace xo {

View file

@ -5,6 +5,7 @@
#pragma once
#include "gc_allocator_traits.hpp"
#include <memory>
#include <cstdint>
@ -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<IAlloc> **/
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)

View file

@ -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 <typename Allocator>
struct gc_allocator_traits : std::allocator_traits<Allocator> {
@ -34,15 +60,15 @@ namespace xo {
// default: allocator A fallback to standard non-gc allocator behavior
template <typename A, typename = void>
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 <typename A>
struct is_incremental_collector<A, std::void_t<typename A::is_incremental_collector>> :
A::is_incremental_collector {};
struct has_incremental_gc_interface<A, std::void_t<typename A::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<Allocator>::value;
static inline constexpr bool has_incremental_gc_interface_v = has_incremental_gc_interface<Allocator>::value;
};
} /*namespace gc*/

View file

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

View file

@ -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@")

View file

@ -112,10 +112,10 @@ namespace xo {
using allocator_traits = std::allocator_traits<Allocator>;
using ReducedValue = typename Reduce::value_type;
using RbTreeLhs = detail::RedBlackTreeLhs<RedBlackTree<Key, Value, Reduce>>;
using RbTreeConstLhs = detail::RedBlackTreeConstLhs<RedBlackTree<Key, Value, Reduce>>;
using RbUtil = detail::RbTreeUtil<Key, Value, Reduce>;
using RbNode = detail::Node<Key, Value, Reduce>;
using RbTreeLhs = detail::RedBlackTreeLhs<RedBlackTree>;
using RbTreeConstLhs = detail::RedBlackTreeConstLhs<RedBlackTree>;
using node_type = RbNode;
using node_allocator_type = typename std::allocator_traits<Allocator>::template rebind_alloc<node_type>;
@ -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<RbNode *>(x);
delete xx;
node_allocator_traits::deallocate(node_alloc_, xx, 1);
// delete x
};
RbUtil::postorder_node_visitor(this->root_,

View file

@ -6,6 +6,7 @@
#pragma once
#include "RbTypes.hpp"
#include "xo/allocutil/gc_allocator_traits.hpp"
#include <utility>
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<ReducedValue, ReducedValue>(leaf_rv, leaf_rv));
} /*make_leaf*/
#endif
/* return #of key/vaue pairs in tree rooted at x. */
static size_t tree_size(Node *x) {

View file

@ -779,7 +779,6 @@ namespace xo {
* - f=false for existing node (k already in tree before this call)
* - n=node containing key k
*/
template<typename NodeAllocator>
static std::pair<bool, RbNode *>
insert_aux(NodeAllocator & alloc,
value_type const & kv_pair,
@ -885,7 +884,6 @@ namespace xo {
* - N has no child nodes
* - N->parent() != nullptr
*/
template <typename NodeAllocator>
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<typename NodeAllocator>
static bool erase_aux(NodeAllocator & alloc,
Key const & k,
Reduce const & reduce_fn,
@ -1556,7 +1553,6 @@ namespace xo {
return true;
} /*erase_aux*/
template <typename NodeAllocator>
static void erase_1child_aux(NodeAllocator & alloc,
RbNode * N,
Reduce const & reduce_fn,

View file

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