xo-XXX -> .xo-XXX (prep subrepo)

This commit is contained in:
Roland Conybeare 2026-06-06 22:00:54 -04:00
commit cf0bd4d975
2105 changed files with 0 additions and 0 deletions

View file

@ -1,272 +0,0 @@
/** @file IAlloc.hpp
*
* @author: Roland Conybeare, Jul 2025
**/
#pragma once
#include "gc_allocator_traits.hpp"
#include <memory>
#include <cstdint>
namespace xo {
template <typename T>
using up = std::unique_ptr<T>;
class IObject;
class Object;
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
*
* In either case deallocation is trivial
**/
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>
*
* want Object here (not IObject) to get Object::_forward_to()
**/
using gc_object_interface = xo::Object;
using has_incremental_gc_interface = std::true_type;
using has_trivial_deallocate = std::true_type;
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) {
constexpr uint32_t c_bpw = c_alloc_alignment;
/* 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);
}
// ----- 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)
* 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; }
/** check write barrier (if impl has write barrier)
* given an object @p parent that contains object pointer @p 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
* *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; }
/** 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
**/
virtual std::byte * alloc_gc_copy(std::size_t /*z*/, const void * /*src*/) {
// LCOV_EXCL_START
//assert(false);
return nullptr;
// LCOV_EXCL_STOP
}
};
/** 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 <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 gc_interface = IAllocPtr;
using has_incremental_gc_interface = IAlloc::has_incremental_gc_interface;
/** rebind is for typed allocators. since IAlloc is untyped,
* rebind is almost trivial
**/
template <typename U>
struct rebind {
using other = allocator<U>;
};
public:
explicit allocator(IAlloc * mm) : impl_{mm} {}
allocator(const allocator &) = default;
allocator & operator=(const allocator &) = default;
template <typename U>
allocator(const allocator<U> & other) : impl_{other.impl_.mm_} {}
pointer allocate(size_type n) {
std::byte * raw = impl_.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);
impl_.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 impl_.mm_ == other.impl_.mm_;
}
/** gc_interface=IAlloc **/
operator gc_interface () const { return impl_; }
public:
IAllocPtr impl_;
};
} /*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<std::byte *>(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<std::byte *>(p));
//}
/* end IAlloc.hpp */

View file

@ -1,137 +0,0 @@
/** @file IObject.hpp
*
* @author Roland Conybeare, Nov 2025
**/
#pragma once
#include <cstddef>
#include <cstdint>
#include <type_traits>
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:
/** 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.
*
* Expect Allocator is xo::gc::allocator<T> (see IAlloc.hpp)
**/
template <typename T, typename Allocator>
void _gc_assign_member(T ** lhs,
T * rhs,
Allocator & alloc)
{
static_assert(std::is_convertible_v<decltype(*lhs), IObject*>);
alloc.impl_.mm_->assign_member(this, reinterpret_cast<IObject **>(lhs), rhs);
}
/** 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;
};
static_assert(std::is_destructible_v<IObject>, "IObject must be destructible");
/** @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

@ -1,57 +0,0 @@
/** @file ObjectVisitor.hpp
*
**/
#pragma once
#include "IAlloc.hpp"
#include <cstdint>
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<T> 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<T>::forward_children() to pick up
* navigation code for such template arguments.
**/
template <typename T>
class ObjectVisitor {
// public:
// void forward_children(T & target,
// IAlloc * gc) { (void)target; (void)gc; }
};
#define XO_TRIVIAL_OBJECT_VISITOR(TYPE) \
template <> \
class ObjectVisitor<TYPE> { \
public: \
static void forward_children(TYPE &, IAlloc *) {} \
}
XO_TRIVIAL_OBJECT_VISITOR(int32_t);
XO_TRIVIAL_OBJECT_VISITOR(double);
} /*namespace gc*/
} /*namespace xo*/
/* ObjectVisitor.hpp */

View file

@ -1,261 +0,0 @@
/** @file gc_allocator_traits.hpp
*
* @author Roland Conybeare, Nov 2025
**/
#pragma once
#include <type_traits>
#include <memory>
#include <cassert>
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<A>
**/
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 <typename T, typename AA>
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; }
};
/** dummy GC interface.
* non-empty intersection with IAlloc
**/
template <typename GcObjectInterface>
struct FallbackGcInterface {
template <typename Allocator>
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<Allocator>
* Introduces additional i/face methods
* for garbage-collector-enabled allocators
*
* Use Cases:
* 1. drop-in replacement for std::allocator_traits<Allocator>
* 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<xo::gc::GC>)
* 3. allows a gc-aware template class T to fallback
* to ordinary allocator-aware behavior for non-gc
* allocators, such as std::allocator<T>,
* 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
* 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 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)
* 2d. implement A::object_interface::_forward_children(alloc)
* 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
* 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> {
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>
struct has_incremental_gc_interface : std::false_type {};
// opt-in: A provides nested type 'has_incremental_collector_interface':
// struct A {
// using has_incremental_collector = std::true_type;
// };
template <typename A>
struct has_incremental_gc_interface<A, std::void_t<typename A::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<Allocator>::value;
// ----------------------------------------------------------------
// default: allocate A fallback to standard non-GC allocator behavior
template <typename A, typename = void>
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 <typename A>
struct has_trivial_deallocate<A, std::void_t<typename A::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<Allocator>::value;
// ----------------------------------------------------------------
// 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<Allocator>::template object_interface<Allocator>
//
template <typename A, typename = void>
struct object_interface : public FallbackObjectInterface {};
// specialization when an allocator A
// (which will actuallly be Allocator via SFINAE)
// provides gc_object_interface
//
template <typename A>
struct object_interface<A, std::void_t<typename 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
// in xo-ordinal-tree)
// can inherit
// gc_allocator_traits<Allocator>::object_interface_type
//
using object_interface_type = object_interface<Allocator>;
// ----------------------------------------------------------------
// 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 <typename A, typename = void>
struct gc_interface : public FallbackGcInterface<object_interface_type> {};
// allocator opt-in by providing a gc_interface type
template <typename A>
struct gc_interface<A, std::void_t<typename A::gc_interface>> : public A::gc_interface {};
// interface for (narrow) GC interaction.
// Construct from allocator
using gc_interface_type = gc_interface<Allocator>;
};
} /*namespace gc*/
} /*namespace xo*/
/* end gc_allocator_traits.hpp */

View file

@ -1,89 +0,0 @@
/** @file gc_ptr.hpp
*
* @author Roland Conybeare, Nov 2025
**/
#pragma once
#include <cstdint>
namespace xo {
template <typename T>
class gc_ptr;
template <typename T>
using gp = gc_ptr<T>;
/** wrapper for a pointer to garbage-collector-eligible T.
* Application code will usually use the alias template gp<T>
**/
template <typename T>
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 <typename S>
gc_ptr(const gc_ptr<S> & x) : ptr_{x.ptr()} {}
/** runtime downcast. shorthand for dynamic_cast<T*> **/
template <typename S>
static gc_ptr<T> from(const gc_ptr<S> & x) { return gc_ptr<T>{dynamic_cast<T*>(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<std::uintptr_t>(x1.ptr());
std::uintptr_t u2 = reinterpret_cast<std::uintptr_t>(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_; }
auto operator<=>(gc_ptr<T> other) const
requires std::three_way_comparable<T>
{
return *ptr_ <=> *other.ptr_;
}
bool operator==(gc_ptr<T> other) const
requires std::equality_comparable<T>
{
return *ptr_ == *other.ptr_;
}
private:
T * ptr_ = nullptr;
};
} /*namespace xo*/
/* end gc_ptr.hpp */