xo-gc: + ACollector.assign_member()

This commit is contained in:
Roland Conybeare 2026-03-24 22:11:25 -04:00
commit 5909e9797f
16 changed files with 128 additions and 20 deletions

View file

@ -8,7 +8,6 @@
#include <xo/arena/MemorySizeInfo.hpp>
#include <xo/arena/AllocError.hpp>
#include "AllocInfo.hpp"
//#include "AllocIterator.hpp"
#include "AllocRange.hpp"
#include "typeseq.hpp"
#include <xo/facet/obj.hpp>

View file

@ -76,6 +76,15 @@ namespace xo {
**/
virtual void request_gc(Opaque d, generation upto) = 0;
/** Assign pointer @p p_lhs to destination @p rhs, within parent allocation @p parent
*
* Require: gc not in progress
**/
virtual void assign_member(Opaque d,
void * parent,
obj<AGCObject> * p_lhs,
obj<AGCObject> & rhs) = 0;
/** evacuate @p *lhs, that refers to state with interface @p lhs_iface,
* to collector @p d's to-space. Replace *lhs_data with forwarding pointer
*

View file

@ -53,6 +53,11 @@ public:
/** @defgroup mm-gcobject-methods **/
///@{
// const methods
/** An uninitialized AGCObject instance will have zero vtable pointer (per {linux,osx} abi).
* Use case for this is narrow. We go to some lengths to avoid null vtable pointers. For example
* obj<AFacet> will have non-null vtable (via IFacet_Any) with all methods terminating.
**/
bool _has_null_vptr() const noexcept { return *reinterpret_cast<const void * const *>(this) == nullptr; }
/** RTTI: unique id# for actual runtime data representation **/
virtual typeseq _typeseq() const noexcept = 0;
/** destroy instance @p d; calls c++ dtor only for actual runtime type; does not recover memory **/

View file

@ -41,6 +41,8 @@ namespace xo {
[[noreturn]] void add_gc_root_poly(Opaque, obj<AGCObject> *) override { _fatal(); }
[[noreturn]] void remove_gc_root_poly(Opaque, obj<AGCObject> *) override { _fatal(); }
[[noreturn]] void request_gc(Opaque, generation) override { _fatal(); }
[[noreturn]] void assign_member(Opaque, void *,
obj<AGCObject> *, obj<AGCObject> &) override { _fatal(); }
[[noreturn]] void forward_inplace(Opaque, AGCObject *, void **) override { _fatal(); }
private:

View file

@ -59,6 +59,10 @@ namespace xo {
void request_gc(Opaque d, generation upto) override {
I::request_gc(_dcast(d), upto);
}
void assign_member(Opaque d, void * parent,
obj<AGCObject> * p_lhs, obj<AGCObject> & rhs) override {
I::assign_member(_dcast(d), parent, p_lhs, rhs);
}
void forward_inplace(Opaque d,
AGCObject * lhs_iface, void ** lhs_data) override {
I::forward_inplace(_dcast(d), lhs_iface, lhs_data);

View file

@ -51,6 +51,11 @@ namespace xo {
void add_gc_root_poly(obj<AGCObject> * p_root) { O::iface()->add_gc_root_poly(O::data(), p_root); }
void remove_gc_root_poly(obj<AGCObject> * p_root) { O::iface()->remove_gc_root_poly(O::data(), p_root); }
void request_gc(generation g) { O::iface()->request_gc(O::data(), g); }
void assign_member(void * parent,
obj<AGCObject> * p_lhs,
obj<AGCObject> & rhs) { O::iface()->assign_member(O::data(), parent, p_lhs, rhs); }
void forward_inplace(AGCObject * lhs_iface, void ** lhs_data) { O::iface()->forward_inplace(O::data(), lhs_iface, lhs_data); }
/** add root @p p_root **/

View file

@ -43,9 +43,27 @@ namespace xo {
void
RCollector<Object>::forward_pivot_inplace(obj<AFacet, DRepr> * p_objs)
{
auto e = xo::facet::FacetRegistry::instance().variant<AGCObject,AFacet>(*p_objs);
this->forward_inplace(e.iface(), (void **)&(p_objs->data_));
if (*p_objs) {
auto e = xo::facet::FacetRegistry::instance().variant<AGCObject,AFacet>(*p_objs);
this->forward_inplace(e.iface(), (void **)&(p_objs->data_));
}
}
/** gc-aware assignment; engage special book-keeping for cross-gen pointers **/
inline void mm_do_assign(obj<ACollector> & gc,
void * parent,
obj<AGCObject> * p_lhs,
obj<AGCObject> & rhs)
{
if (gc.data()) {
gc.assign_member(parent, p_lhs, rhs);
} else {
// assume null collector downstream from allocator that does not provide collection.
// In that no additional assignment work.
*p_lhs = rhs;
}
};
}
}

View file

@ -23,6 +23,9 @@ namespace xo {
explicit constexpr generation(value_type x) : value_{x} {}
static generation nursery() { return generation{0}; }
static generation sentinel() { return generation(c_max_generation); }
bool is_sentinel() const noexcept { return value_ == c_max_generation; }
constexpr operator value_type() const { return value_; }
@ -30,6 +33,19 @@ namespace xo {
std::uint32_t value_ = 0;
};
inline bool operator==(generation lhs, generation rhs) {
return lhs.value_ == rhs.value_;
}
inline bool operator<(generation lhs, generation rhs) {
return lhs.value_ < rhs.value_;
}
inline bool operator>(generation lhs, generation rhs) {
return lhs.value_ > rhs.value_;
}
}
}

View file

@ -46,6 +46,11 @@ public:
/** @defgroup mm-resourcevisitor-methods **/
///@{
// const methods
/** An uninitialized AResourceVisitor instance will have zero vtable pointer (per {linux,osx} abi).
* Use case for this is narrow. We go to some lengths to avoid null vtable pointers. For example
* obj<AFacet> will have non-null vtable (via IFacet_Any) with all methods terminating.
**/
bool _has_null_vptr() const noexcept { return *reinterpret_cast<const void * const *>(this) == nullptr; }
/** RTTI: unique id# for actual runtime data representation **/
virtual typeseq _typeseq() const noexcept = 0;
/** destroy instance @p d; calls c++ dtor only for actual runtime type; does not recover memory **/

View file

@ -11,7 +11,9 @@
namespace xo {
namespace mm {
/*
/**
* @brief specifies alloc header layout
*
* Each allocation is preceded by a 64-bit header.
* Header is split into 3 configurable-width bit fields,
* labelled (from hi to lo bit order) {tseq, age, size}.
@ -36,7 +38,7 @@ namespace xo {
* 0..............01111111 gen_mask_unshifted
* 0..011111110..........0 gen_mask_shifted
* > < gen_shift
*/
**/
struct AllocHeaderConfig {
using repr_type = AllocHeader;
using span_type = std::pair<const std::byte *, const std::byte *>;

View file

@ -103,6 +103,9 @@ namespace xo {
/** @defgroup mm-arena-methods **/
///@{
/** false -> not eligible for GC (allocates own memory + not moveable) **/
static constexpr bool is_gc_eligible() { return false; }
/** Reserved memory, in bytes. This is the maximum size of this arena. **/
size_type reserved() const noexcept { return hi_ - lo_; }
/** Allocated memory in bytes: memory consumed by allocs from this arena,
@ -125,6 +128,9 @@ namespace xo {
**/
bool contains(const void * addr) const noexcept { return (lo_ <= addr) && (addr < hi_); }
/** Truee iff address @p addr is owned by this arena and in allocated regions **/
bool contains_allocated(const void * addr) const noexcept { return (lo_ <= addr) && (addr < free_); }
/** true if arena is mapped i.e. has a reserved address range **/
bool is_mapped() const noexcept { return (lo_ != nullptr) && (hi_ != nullptr); }
@ -147,6 +153,8 @@ namespace xo {
/** get header from allocated object address **/
header_type * obj2hdr(void * obj) noexcept;
/** get header from allocated object address (const version) **/
const header_type * obj2hdr(void * obj) const noexcept;
/** report alloc book-keeping info for allocation at @p mem
*

View file

@ -68,6 +68,9 @@ namespace xo {
size_type hint_max_capacity = 0,
bool debug_flag = false);
/** true for types that support the AGCObject facet; DArenaHashMap gets its own memory! **/
static constexpr bool is_gc_eligible() { return false; }
size_type empty() const noexcept { return store_.empty(); }
size_type groups() const noexcept { return store_.n_group_; }
size_type size() const noexcept { return store_.size_; }

View file

@ -76,6 +76,7 @@ namespace xo {
const_iterator cend() const noexcept { return this->_address_of(size_); }
const_iterator end() const noexcept { return this->cend(); }
constexpr const DArena * store() const { return &store_; }
constexpr T * data() { return reinterpret_cast<T*>(store_.lo_); }
constexpr const T * data() const { return reinterpret_cast<const T*>(store_.lo_); }
@ -223,7 +224,7 @@ namespace xo {
//
store_.restore(zero_ckp_);
store_.alloc(xo::reflect::typeseq::id<std::byte>(), req_z);
this->size_ = z;
}

View file

@ -170,8 +170,17 @@ namespace xo {
return (header_type *)((byte *)obj - sizeof(header_type));
}
auto
DArena::obj2hdr(void * obj) const noexcept -> const header_type *
{
assert(config_.store_header_flag_);
return (const header_type *)((byte *)obj - sizeof(header_type));
}
void
DArena::visit_pools(const MemorySizeVisitor & fn) const {
DArena::visit_pools(const MemorySizeVisitor & fn) const
{
/** arena can't tell purpose of allocated memory;
* must assume it's all used
**/

View file

@ -31,6 +31,7 @@ namespace xo {
using repr_type = xo::map::DArenaHashMap<key_type, Binding::slot_type>;
using ACollector = xo::mm::ACollector;
using AAllocator = xo::mm::AAllocator;
using AGCObject = xo::mm::AGCObject;
using MemorySizeVisitor = xo::mm::MemorySizeVisitor;
using ppindentinfo = xo::print::ppindentinfo;
using size_type = std::uint32_t;
@ -47,10 +48,16 @@ namespace xo {
* Use memory from @p mm for DGlobalSymtab instance.
* Hashmap for variables per @p var_cfg; for types per @p type_cfg.
**/
static dp<DGlobalSymtab> make(obj<AAllocator> mm,
obj<AAllocator> fixed_mm,
const ArenaHashMapConfig & var_cfg,
const ArenaHashMapConfig & type_cfg);
static DGlobalSymtab * _make(obj<AAllocator> mm,
obj<AAllocator> fixed_mm,
const ArenaHashMapConfig & var_cfg,
const ArenaHashMapConfig & type_cfg);
/** like _make(..), but create fop **/
static obj<AGCObject,DGlobalSymtab> make(obj<AAllocator> mm,
obj<AAllocator> fixed_mm,
const ArenaHashMapConfig & var_cfg,
const ArenaHashMapConfig & type_cfg);
/** non-trivial destructor for @ref map_ **/
~DGlobalSymtab() = default;

View file

@ -3,7 +3,7 @@
* @author Roland Conybeare, Jan 2026
**/
#include "DGlobalSymtab.hpp"
#include "GlobalSymtab.hpp"
#include "Typename.hpp"
#include "Binding.hpp"
#include "DUniqueString.hpp"
@ -29,11 +29,11 @@ namespace xo {
{
}
dp<DGlobalSymtab>
DGlobalSymtab::make(obj<AAllocator> mm,
obj<AAllocator> aux_mm,
const ArenaHashMapConfig & var_cfg,
const ArenaHashMapConfig & type_cfg)
DGlobalSymtab *
DGlobalSymtab::_make(obj<AAllocator> mm,
obj<AAllocator> aux_mm,
const ArenaHashMapConfig & var_cfg,
const ArenaHashMapConfig & type_cfg)
{
/* note: using aux_mm for DArenaHashMap superstructure.
* {variable, type} storage allocated from mm.
@ -51,14 +51,26 @@ namespace xo {
DArray * types = DArray::empty(mm, type_map->capacity());
auto symtab = dp<DGlobalSymtab>::make(mm,
std::move(var_map), vars,
std::move(type_map), types);
void * mem = mm.alloc_for<DGlobalSymtab>();
auto symtab = new (mem) DGlobalSymtab(std::move(var_map),
vars,
std::move(type_map),
types);
assert(symtab);
return symtab;
}
obj<AGCObject,DGlobalSymtab>
DGlobalSymtab::make(obj<AAllocator> mm,
obj<AAllocator> aux_mm,
const ArenaHashMapConfig & var_cfg,
const ArenaHashMapConfig & type_cfg)
{
return obj<AGCObject,DGlobalSymtab>(_make(mm, aux_mm, var_cfg, type_cfg));
}
void
DGlobalSymtab::visit_pools(const MemorySizeVisitor & visitor) const
{
@ -275,6 +287,9 @@ namespace xo {
{
// map_ doesn't contain any gc-owned data, can skip
static_assert(var_map_.is_gc_eligible() == false);
static_assert(type_map_.is_gc_eligible() == false);
gc.forward_inplace(&vars_);
gc.forward_inplace(&types_);