/* Object.hpp * * author: Roland Conybeare, Jul 2025 */ #pragma once #include "xo/reflect/TaggedPtr.hpp" #include "IAlloc.hpp" #include #include namespace xo { namespace gc { class GC; class ObjectStatistics; }; template class gc_ptr; template using gp = gc_ptr; /** wrapper for a pointer to garbage-collector-eligible T. * Application code will usually use the alias template gp **/ template 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 gc_ptr(const gc_ptr & x) : ptr_{x.ptr()} {} static bool is_eq(gc_ptr x1, gc_ptr x2) { std::uintptr_t u1 = reinterpret_cast(x1.ptr()); std::uintptr_t u2 = reinterpret_cast(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)); } 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: * * relying on inheritance means we insist that GC traits * for a type appear directly in that type's vtable, and at specific locations. * This implies one level of indirection when GC traverses an instance. * * 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 { public: using TaggedPtr = xo::reflect::TaggedPtr; public: virtual ~Object() = default; /** memory allocator for objects. Likely this will be a GC instance, * but simple arena also supported. **/ static gc::IAlloc * mm; /** assign value @p rhs to member @p *lhs of @p parent. * if assignment creates a cross-generational or cross-checkpoint pointer, * add mutation log entry **/ template static void assign_member(gp parent, gp * lhs, gp rhs); /** use from GC aux functions **/ static gc::GC * _gc() { return reinterpret_cast(mm); } /** during GC * 1. copy destination object @p *addr to (new) to-space. * 2. overwrite existing object @p *addr with a forwarding pointer to * copy made in step 1. * 3. return the location of the copy make in step 1. * * @p src. source object to be forwarded * @p gc. garbage collector */ static Object * _forward(Object * src, gc::GC * gc); template static void _forward_inplace(T ** src_addr) { Object * fwd = _forward(*src_addr, _gc()); *src_addr = reinterpret_cast(fwd); } template static void _forward_inplace(gp & src) { _forward_inplace(src.ptr_address()); } /** primary workhorse for garbage collection. * * we assign each object one of three colors: black|gray|white. * * color | location | children | action | * ------+------------+------------+-------------------------+ * black | from-space | any | move to to-space | * gray | to-space | any | move remaining children | * white | to-space | white/gray | done | * * initially all reachable objects are black. * GC is complete when all reachable objects are white. * GC needs a variable amount of temporary storage to keep track of all gray objects * * Evacuate reachable object graph rooted at @p src to to-space. * On return all objects reachable from @p src are white * * @param src address of object to evacuate * @param gc garbage collector * @param stats per-object-type GC statistics **/ static Object * _deep_move(Object * src, gc::GC * gc, gc::ObjectStatistics * stats); /** copy @p src to to-space, and replace original with forwarding pointer to new location. * return the new location **/ static Object * _shallow_move(Object * src, gc::GC * gc); // Reflection support /** tagged pointer with runtime type information **/ virtual TaggedPtr self_tp() const = 0; /** print on stream @p os **/ virtual void display(std::ostream & os) const = 0; // GC support /** 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 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() 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() = 0; }; template void Object::assign_member(gp parent, gp * lhs, gp rhs) { Object::mm->assign_member(parent.ptr(), reinterpret_cast(lhs->ptr_address()), rhs.ptr()); } std::ostream & operator<< (std::ostream & os, gp x); /** @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 Object * src) : mm_{mm}, src_{src} {} gc::IAlloc * mm_ = nullptr; const void * src_ = nullptr; }; } /*namespace xo*/ void * operator new (std::size_t z, const xo::Cpof & copy); /* end Object.hpp */