xo-alloc + xo-allocutil: refactor to shrink dep surface area
This commit is contained in:
parent
8d4649c6cf
commit
540b43d971
34 changed files with 479 additions and 323 deletions
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "IAlloc.hpp"
|
||||
#include "xo/allocutil/IAlloc.hpp"
|
||||
#include "ObjectStatistics.hpp"
|
||||
|
||||
namespace xo {
|
||||
|
|
@ -175,8 +175,8 @@ namespace xo {
|
|||
virtual void clear() final override;
|
||||
virtual void checkpoint() final override;
|
||||
virtual std::byte * alloc(std::size_t z) final override;
|
||||
virtual bool check_owned(IObject * src) const final override;
|
||||
|
||||
virtual bool check_owned(Object * src) const final override;
|
||||
|
||||
ArenaAlloc & operator=(const ArenaAlloc &) = delete;
|
||||
ArenaAlloc & operator=(ArenaAlloc &&) = delete;
|
||||
|
|
|
|||
|
|
@ -19,18 +19,18 @@ namespace xo {
|
|||
**/
|
||||
class Forwarding1 : public Object {
|
||||
public:
|
||||
explicit Forwarding1(gp<Object> dest);
|
||||
explicit Forwarding1(gp<IObject> dest);
|
||||
|
||||
// inherited from Object..
|
||||
virtual TaggedPtr self_tp() const final override;
|
||||
virtual void display(std::ostream & os) const final override;
|
||||
virtual bool _is_forwarded() const final override { return true; }
|
||||
virtual Object * _offset_destination(Object * src) const final override;
|
||||
virtual Object * _destination() final override;
|
||||
virtual IObject * _offset_destination(IObject * src) const final override;
|
||||
virtual IObject * _destination() final override;
|
||||
/** required by Object i/face, but never called on Forwarding1 **/
|
||||
virtual std::size_t _shallow_size() const final override;
|
||||
/** required by Object i/face, but never called on Forwarding1 **/
|
||||
virtual Object * _shallow_copy(gc::IAlloc * mm) const final override;
|
||||
virtual IObject * _shallow_copy(gc::IAlloc * mm) const final override;
|
||||
/** required by Object i/face, but never called on Forwarding1 **/
|
||||
virtual std::size_t _forward_children(gc::IAlloc * mm) final override;
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ namespace xo {
|
|||
* UB revealed when GC traverses a pointer that relies on the 2nd
|
||||
* vtable to index virtual methods.
|
||||
**/
|
||||
gp<Object> dest_;
|
||||
gp<IObject> dest_;
|
||||
};
|
||||
|
||||
} /*namespace obj*/
|
||||
|
|
|
|||
|
|
@ -88,29 +88,30 @@ namespace xo {
|
|||
|
||||
class MutationLogEntry {
|
||||
public:
|
||||
MutationLogEntry(Object * parent, Object ** lhs) : parent_{parent}, lhs_{lhs} {}
|
||||
MutationLogEntry(IObject * parent, IObject ** lhs)
|
||||
: parent_{parent}, lhs_{lhs} {}
|
||||
|
||||
Object * parent() const { return parent_; }
|
||||
Object ** lhs() const { return lhs_; }
|
||||
IObject * parent() const { return parent_; }
|
||||
IObject ** lhs() const { return lhs_; }
|
||||
|
||||
Object * child() const { return *lhs_; }
|
||||
IObject * child() const { return *lhs_; }
|
||||
|
||||
bool is_child_forwarded() const;
|
||||
bool is_parent_forwarded() const;
|
||||
|
||||
Object * parent_destination() const;
|
||||
IObject * parent_destination() const;
|
||||
|
||||
/** Flag obsolete mutation.
|
||||
* Future proofing, never happens for regular objects
|
||||
**/
|
||||
bool is_dead() const { return false; }
|
||||
|
||||
MutationLogEntry update_parent_moved(Object * parent_to) const;
|
||||
void fixup_parent_child_moved(Object * child_to) { *lhs_ = child_to; }
|
||||
MutationLogEntry update_parent_moved(IObject * parent_to) const;
|
||||
void fixup_parent_child_moved(IObject * child_to) { *lhs_ = child_to; }
|
||||
|
||||
private:
|
||||
Object * parent_;
|
||||
Object ** lhs_;
|
||||
IObject * parent_ = nullptr;
|
||||
IObject ** lhs_ = nullptr;
|
||||
};
|
||||
|
||||
using MutationLog = std::vector<MutationLogEntry>;
|
||||
|
|
@ -235,15 +236,15 @@ namespace xo {
|
|||
/** add gc root at address @p addr . Gc will keep alive anything reachable
|
||||
* from @c *addr
|
||||
**/
|
||||
void add_gc_root(Object ** addr);
|
||||
void add_gc_root(IObject ** addr);
|
||||
/** reverse the effect of previous call to @ref add_gc_root **/
|
||||
void remove_gc_root(Object ** addr);
|
||||
void remove_gc_root(IObject ** addr);
|
||||
|
||||
/** convenience wrapper **/
|
||||
template <typename T>
|
||||
void add_gc_root_dwim(gp<T> * p) { this->add_gc_root(reinterpret_cast<Object**>(p->ptr_address())); }
|
||||
void add_gc_root_dwim(gp<T> * p) { this->add_gc_root(reinterpret_cast<IObject**>(p->ptr_address())); }
|
||||
template <typename T>
|
||||
void remove_gc_root_dwim(gp<T> * p) { this->remove_gc_root(reinterpret_cast<Object**>(p->ptr_address())); }
|
||||
void remove_gc_root_dwim(gp<T> * p) { this->remove_gc_root(reinterpret_cast<IObject**>(p->ptr_address())); }
|
||||
|
||||
/** may optionally use this to observe GC copy phase.
|
||||
* Will be invoked once _per surviving object_, so not cheap.
|
||||
|
|
@ -308,17 +309,17 @@ namespace xo {
|
|||
* @param lhs. address of a member variable within the allocation of @p parent.
|
||||
* @param rhs. new target for @p *lhs
|
||||
**/
|
||||
virtual void assign_member(Object * parent, Object ** lhs, Object* rhs) final override;
|
||||
virtual void assign_member(IObject * parent, IObject ** lhs, IObject* rhs) final override;
|
||||
|
||||
/** during GC check for source objects owned by GC.
|
||||
* See Object::_shallow_move.
|
||||
**/
|
||||
virtual bool check_owned(Object * src) const final override;
|
||||
virtual bool check_owned(IObject * src) const final override;
|
||||
/** queries during GC to determine if object at address @p src should move:
|
||||
* - full GC -> always
|
||||
* - incr GC -> if not tenured
|
||||
**/
|
||||
virtual bool check_move(Object * src) const final override;
|
||||
virtual bool check_move(IObject * src) const final override;
|
||||
virtual std::byte * alloc(std::size_t z) final override;
|
||||
virtual std::byte * alloc_gc_copy(std::size_t z, const void * src) final override;
|
||||
|
||||
|
|
@ -349,7 +350,7 @@ namespace xo {
|
|||
/** scan to-space for object statistics before GC */
|
||||
void capture_object_statistics(generation upto, capture_phase phase);
|
||||
/** copy object **/
|
||||
void copy_object(Object ** addr, generation upto, ObjectStatistics * object_stats);
|
||||
void copy_object(IObject ** addr, generation upto, ObjectStatistics * object_stats);
|
||||
/** copy everything reachable from global gc roots **/
|
||||
void copy_globals(generation g);
|
||||
/** review mutation log; may discover+rescue reachable objects.
|
||||
|
|
@ -426,7 +427,7 @@ namespace xo {
|
|||
* Application can introduce new root object pointers at any time provided GC not running,
|
||||
* but cannot withdraw them.
|
||||
**/
|
||||
std::vector<Object**> gc_root_v_;
|
||||
std::vector<IObject**> gc_root_v_;
|
||||
|
||||
/** log cross-generational and cross-checkpoint mutations.
|
||||
* These need to be adjusted on next incremental collection
|
||||
|
|
|
|||
|
|
@ -1,129 +0,0 @@
|
|||
/* 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 Object;
|
||||
|
||||
namespace gc {
|
||||
/** @class IAllocator
|
||||
* @brief arena allocation interface with limited garbage collector support
|
||||
*
|
||||
* 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() {}
|
||||
|
||||
/** compute padding to add to an allocation of size z to bring it up to
|
||||
* a multiple of word size (8 bytes on x86_64)
|
||||
**/
|
||||
static std::uint32_t alloc_padding(std::size_t z);
|
||||
/** z + alloc_padding(z) **/
|
||||
static std::size_t with_padding(std::size_t 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(Object * src) const;
|
||||
/** true iff object at address @p src must move as part of
|
||||
* in-progress collection phase
|
||||
**/
|
||||
virtual bool check_move(Object * src) const;
|
||||
/** 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(Object * parent, Object ** lhs, Object * 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);
|
||||
};
|
||||
} /*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 */
|
||||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
#include "xo/reflect/TaggedPtr.hpp"
|
||||
#include "IAlloc.hpp"
|
||||
#include "xo/allocutil/IObject.hpp"
|
||||
#include "xo/allocutil/gc_ptr.hpp"
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
|
||||
|
|
@ -16,71 +18,6 @@ namespace xo {
|
|||
class ObjectStatistics;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/** Root class for all xo GC-collectable objects.
|
||||
*
|
||||
* Design note:
|
||||
|
|
@ -92,11 +29,15 @@ namespace xo {
|
|||
* Would be feasible to relax the must-inherit-from-Object constraint
|
||||
* by having GC use its own wrapper, at cost of an extra layer of indirection
|
||||
**/
|
||||
class Object {
|
||||
class Object : public IObject {
|
||||
public:
|
||||
using TaggedPtr = xo::reflect::TaggedPtr;
|
||||
|
||||
public:
|
||||
static gp<Object> from(gp<IObject> x) {
|
||||
return dynamic_cast<Object*>(x.ptr());
|
||||
}
|
||||
|
||||
virtual ~Object() = default;
|
||||
|
||||
/** memory allocator for objects. Likely this will be a GC instance,
|
||||
|
|
@ -111,7 +52,7 @@ namespace xo {
|
|||
* add mutation log entry
|
||||
**/
|
||||
template <typename T>
|
||||
static void assign_member(gp<Object> parent, gp<T> * lhs, gp<Object> rhs);
|
||||
static void assign_member(gp<IObject> parent, gp<T> * lhs, gp<IObject> rhs);
|
||||
|
||||
/** use from GC aux functions **/
|
||||
static gc::GC * _gc() { return reinterpret_cast<gc::GC*>(mm); }
|
||||
|
|
@ -125,11 +66,11 @@ namespace xo {
|
|||
* @p src. source object to be forwarded
|
||||
* @p gc. garbage collector
|
||||
*/
|
||||
static Object * _forward(Object * src, gc::IAlloc * gc);
|
||||
static IObject * _forward(IObject * src, gc::IAlloc * gc);
|
||||
|
||||
template <typename T>
|
||||
static void _forward_inplace(T ** src_addr, gc::IAlloc * gc) {
|
||||
Object * fwd = _forward(*src_addr, gc);
|
||||
IObject * fwd = _forward(*src_addr, gc);
|
||||
|
||||
*src_addr = reinterpret_cast<T *>(fwd);
|
||||
}
|
||||
|
|
@ -160,12 +101,12 @@ namespace xo {
|
|||
* @param gc garbage collector
|
||||
* @param stats per-object-type GC statistics
|
||||
**/
|
||||
static Object * _deep_move(Object * src, gc::GC * gc, gc::ObjectStatistics * stats);
|
||||
static IObject * _deep_move(IObject * src, gc::GC * gc, gc::ObjectStatistics * stats);
|
||||
|
||||
/** copy @p src to to-space. Overwrite original with forwarding pointer to new location.
|
||||
* return the new location
|
||||
**/
|
||||
static Object * _shallow_move(Object * src, gc::IAlloc * gc);
|
||||
static IObject * _shallow_move(IObject * src, gc::IAlloc * gc);
|
||||
|
||||
// Reflection support
|
||||
|
||||
|
|
@ -176,89 +117,25 @@ namespace xo {
|
|||
/** print on stream @p os **/
|
||||
virtual void display(std::ostream & os) const = 0;
|
||||
|
||||
// GC support
|
||||
// Inherited from IObject..
|
||||
|
||||
/** 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; }
|
||||
//virtual bool _is_forwarded() const override { return false; }
|
||||
//virtual IObject * _offset_destination(IObject * src) const override { return src; };
|
||||
virtual void _forward_to(IObject * dest) override;
|
||||
//virtual IObject * _destination() override { return nullptr; }
|
||||
|
||||
/** offset for uncommon situation where pointer address is offset from object
|
||||
* base address
|
||||
**/
|
||||
virtual Object * _offset_destination(Object * src) const { return src; };
|
||||
|
||||
/** replace this object with a forwarding pointer referring to @p dest.
|
||||
**/
|
||||
virtual void _forward_to(Object * dest);
|
||||
|
||||
/** 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 Object * _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 Object * _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;
|
||||
virtual std::size_t _shallow_size() const override = 0;
|
||||
virtual IObject * _shallow_copy(gc::IAlloc * gc) const override = 0;
|
||||
virtual std::size_t _forward_children(gc::IAlloc * gc) override = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
Object::assign_member(gp<Object> parent, gp<T> * lhs, gp<Object> rhs)
|
||||
Object::assign_member(gp<IObject> parent, gp<T> * lhs, gp<IObject> rhs)
|
||||
{
|
||||
Object::mm->assign_member(parent.ptr(),
|
||||
reinterpret_cast<Object **>(lhs->ptr_address()),
|
||||
rhs.ptr());
|
||||
Object::mm->assign_member(reinterpret_cast<IObject *>(parent.ptr()),
|
||||
reinterpret_cast<IObject **>(lhs->ptr_address()),
|
||||
reinterpret_cast<IObject *>(rhs.ptr()));
|
||||
}
|
||||
|
||||
std::ostream &
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue