xo-alloc + xo-allocutil: refactor to shrink dep surface area

This commit is contained in:
Roland Conybeare 2025-12-01 01:20:49 -05:00
commit 540b43d971
34 changed files with 479 additions and 323 deletions

View file

@ -0,0 +1,154 @@
/** @file IAlloc.hpp
*
* @author: Roland Conybeare, Jul 2025
**/
#pragma once
#include <memory>
#include <cstdint>
namespace xo {
template <typename T>
using up = std::unique_ptr<T>;
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<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

@ -0,0 +1,97 @@
/** @file IObject.hpp
*
* @author Roland Conybeare, Nov 2025
**/
#pragma once
#include <cstddef>
#include <cstdint>
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 */

View file

@ -0,0 +1,75 @@
/** @file gc_allocator_traits.hpp
*
* @author Roland Conybeare, Nov 2025
**/
#pragma once
#include <type_traits>
#include <memory>
namespace xo {
namespace gc {
/** Extended version of
* std::allocator_traits<Allocator>
* 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 <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;
// default: allocator A fallback to standard non-gc allocator behavior
template <typename A, typename = void>
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 <typename A>
struct is_incremental_collector<A, std::void_t<typename A::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<Allocator>::template object_interface<Allocator>
//
template <typename A, typename = void>
struct object_interface {};
// specialization when A provides gc_object_interface
template <typename A>
struct object_interface<A, std::void_t<typename A::gc_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<Allocator>::value;
};
} /*namespace gc*/
} /*namespace xo*/
/* end gc_allocator_traits.hpp */

View file

@ -0,0 +1,77 @@
/** @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_; }
private:
T * ptr_ = nullptr;
};
} /*namespace xo*/
/* end gc_ptr.hpp */