xo-gc/include/xo/gc/GCObjectStore.hpp
Roland Conybeare 134f5614a3 xo-gc: streamline: drop .visit_child methods
no longer needed, thanks to DGCObjectStoreVisitor
2026-04-12 15:00:48 -04:00

270 lines
12 KiB
C++

/** @file GCObjectStore.hpp
*
* @author Roland Conybeare, Apr 2026
**/
#pragma once
#include "GCObjectStoreConfig.hpp"
#include "ObjectTypeSlot.hpp"
#include "object_age.hpp"
#include <xo/alloc2/role.hpp>
#include <array>
namespace xo {
namespace mm {
class X1VerifyStats;
/** @brief container to hold gc-aware objects for X1 collector
*
* Note: X1VerifyStats are in DX1Collector.
* They need to be there, since also interact with MutationLogStore.
* This is reason for DX1Collector to invoke .verify_aux()
* so it can supply X1VerifyStats location
**/
class GCObjectStore {
public:
using ObjectTypeTable = DArenaVector<ObjectTypeSlot>;
/* TODO: AllocIterator pointing to free pointer instead of std::byte* */
using GCMoveCheckpoint = std::array<std::byte *, c_max_generation>;
using header_type = DArena::header_type;
using value_type = DArena::value_type;
using size_type = DArena::size_type;
using typeseq = xo::reflect::typeseq;
public:
explicit GCObjectStore(const GCObjectStoreConfig & cfg, X1VerifyStats * p_verify_stats);
const GCObjectStoreConfig & config() const noexcept { return config_; }
const ObjectTypeTable * get_object_types() const noexcept { return &object_types_; }
const DArena * get_space(Role r, Generation g) const noexcept { return space_[r][g]; }
const DArena * from_space(Generation g) const noexcept { return this->get_space(Role::from_space(), g); }
const DArena * to_space(Generation g) const noexcept { return this->get_space(Role::to_space(), g); }
const DArena * new_space() const noexcept { return this->get_space(Role::to_space(), Generation{0}); }
DArena * get_space(Role r, Generation g) noexcept { return space_[r][g]; }
DArena * from_space(Generation g) noexcept { return this->get_space(Role::from_space(), g); }
DArena * to_space(Generation g) noexcept { return this->get_space(Role::to_space(), g); }
DArena * new_space() noexcept { return this->get_space(Role::to_space(), Generation{0}); }
X1VerifyStats * verify_stats() noexcept { return p_verify_stats_; }
/** true iff type with id @p tseq has known metadata
* (i.e. has appeared in preceding call to install_type
* for this collector)
**/
bool is_type_installed(typeseq tseq) const noexcept;
/** lookup interface from type sequence
* (can use tseq = typeseq::id<T>() for type T)
**/
AGCObject * lookup_type(typeseq tseq) const noexcept;
/** generation to which pointer @p addr belongs, given Role @p r;
* sentinel if not found in this collector
**/
Generation generation_of(Role r, const void * addr) const noexcept;
/** return details from last error (i.e. from g0 to-space) **/
AllocError last_error() const noexcept;
/** get allocation size from header **/
std::size_t header2size(header_type hdr) const noexcept;
/** get generation counter from alloc header **/
object_age header2age(header_type hdr) const noexcept;
/** get tseq from alloc header **/
uint32_t header2tseq(header_type hdr) const noexcept;
/** true iff original alloc has been replaced by a forwarding pointer **/
bool is_forwarding_header(header_type hdr) const noexcept;
/** Retreive bookkeeping info for allocation at @p mem. **/
AllocInfo alloc_info(value_type mem) const noexcept;
/** Call @p visitor for each memory pool owned by this store **/
void visit_pools(const MemorySizeVisitor & visitor) const;
/** true iff address @p addr allocated from this collector
* in Role @p r (according to current GC state)
**/
bool contains(Role r, const void * addr) const noexcept;
/** true iff address @p addr allocated from this collector and currently live
* in Role @p r (according to current GC state)
*
* (i.e. in [lo,free) for an arena)
**/
bool contains_allocated(Role r, const void * addr) const noexcept;
/** Report per-age-bucket information as an array of dictionaries.
* Scans to-space to count per-age statistics.
* Each dictionary has keys "n-live" and "bytes".
* Array index corresponds to object age.
*
* @p mm allocate stats from this allocator.
* @p error_mm allocator for error reporting when out-of-memory.
* @p p_output on exit @p *p_output contains stats array
**/
bool report_object_types(obj<AAllocator> mm,
obj<AAllocator> error_mm,
obj<AGCObject> * p_output) const noexcept;
/** Report per-age-bucket information as an array of dictionaries.
* Scans to-space to count per-age statistics.
* Each dictionary has keys "n-live" and "bytes".
* Array index corresponds to object age.
*
* @p mm allocate stats from this allocator.
* @p error_mm allocator for error reporting when out-of-memory.
* @p p_output on exit @p *p_output contains stats array
**/
bool report_object_ages(obj<AAllocator> mm,
obj<AAllocator> error_mm,
obj<AGCObject> * p_output) const noexcept;
/** snap checkpoint containing allocator state
* use to detect forwarding activity after visiting objects
**/
GCMoveCheckpoint snap_move_checkpoint(Generation upto);
/** verify consistency of this object store, on behalf of collector @p gc.
* Advancing counters in @p *p_verify_stats.
*
* @p gc argument is load-bearing so we have collector interface
* to call AGCObject visitor method (forward_children()) on each
* object stored here.
**/
void verify_ok() noexcept;
/** Register object type with this collector.
* Provides shallow copy and pointer forwarding for instances of this
* type.
**/
bool install_type(const AGCObject & meta) noexcept;
/** For each generation g in [0 ,.., upto)
* swap arenas assigned to {to-space, from-space}.
* Invoked once at the beginning of each gc cycle.
**/
void swap_roles(Generation upto) noexcept;
/** move subgraph at @p root to to-space on behalf of collector @p gc
* Special behavior relative to @ref _deep_move_interior :
* If @p root is not in gc-space, visit immediate children and move them in place (!).
*
* @return new address for @p from_src
*
* Require: runstate_.is_running()
**/
void * deep_move_root(const AGCObject * root_iface,
void ** root_data,
Generation upto);
/** move interior subgraph at @p from_src to to-space.
* no-op if not in gc-space.
*
* NOTE: load-bearing for MutationLogStore
**/
void * deep_move_interior(obj<AGCObjectVisitor> gc,
void * from_src,
Generation upto);
/** Cleanup at the end of a gc cycle.
* Reset from-space
* (current from-space is former to-space,
* relabeled at the beginning of collector cycle)
* for generations in [0 ,.., upto)
**/
void cleanup_phase(Generation upto,
bool sanitize_flag);
private:
/** configure @ref object_types_, using @p page_z **/
void _init_object_types(std::size_t page_z);
/** auxiliary init function **/
void _init_space();
/** true iff {@p alloc_hdr, @p object_data} should move for
* a collection of all generations strictly younger than @p upto.
*
* Require: runstate_.is_running()
**/
bool _check_move_policy(header_type alloc_hdr,
void * gco_data,
Generation upto) const noexcept;
/** Common driver for _deep_move_root(), _deep_move_interior().
* Move object subgraph @p from_src on behalf of @p gc collection cycle,
* covering generations in [0 ,.., upto).
**/
void * _deep_move_gc_owned(obj<AGCObjectVisitor> gc,
void * from_src,
Generation upto);
/** traverse objects allocated after @p ckp, to make sure their children
* are forwarded. Repeat until traverse doesn't find any unforwarded children.
*
* 1. Breadth-first implementation, bad for memory locality
* 2. Need @p gc for per-object-type forward_children api
**/
void _forward_children_until_fixpoint(obj<AGCObjectVisitor> gc,
Generation upto,
GCMoveCheckpoint gray_lo_v);
/** during a gc cycle:
* evacuate object @p from_src, with gc-object interface @p iface.
* Shallow: does not traverse children
**/
void * _shallow_move(obj<AGCObjectVisitor> gc,
AGCObject * iface,
void * from_src);
/** Evacuate object at @p *lhs_data to to-space, during collection phase
* acting on generations g in [0 ,.., upto).
* Need @p gc to pass to invoke AGCObject methods shallow_copy() and
* forward_children()
*
* Replace original with forwarding pointer to new location
**/
void _forward_inplace_aux(obj<AGCObjectVisitor> gc,
AGCObject * lhs_iface,
void ** lhs_data,
Generation upto);
/** categorize fop {@p lhs_iface, @p lhs_data}
* based on location of @p lhs_data.
* Update @ref p_verify_stats_ based on the result:
* increment exactly one of {n_from_, n_to_, n_ext_}
**/
void _verify_aux(AGCObject * lhs_iface,
void * lhs_data);
friend class DGCObjectStoreVisitor;
private:
/** configuration for gc-aware object store **/
GCObjectStoreConfig config_;
/** gc-aware object types **/
ObjectTypeTable object_types_;
/** arena objects for collector managed memory
* 1:1 with roles, but polarity reverses for each collection
**/
std::array<DArena, c_max_generation> space_storage_[c_n_role];
/** arena pointers. The roles of space_storage_[0][g] and space_storage_[1][g]
* are reversed each time generation g gets collected.
**/
std::array<DArena*, c_max_generation> space_[c_n_role];
/** dedicated counters. updated by .verify_aux() **/
X1VerifyStats * p_verify_stats_ = nullptr;
};
} /*namespace mm*/
} /*namespace xo*/
/* end GCObjectStore.hpp */