/** @file DX1Collector.hpp * * @author Roland Conybeare, Dec 2025 **/ #pragma once #include "GCObject.hpp" #include "generation.hpp" #include "object_age.hpp" #include "role.hpp" #include #include #include #include #include namespace xo { namespace mm { template using up = std::unique_ptr; #ifdef NOT_YET /** State associated with a single DX1Collector generation **/ struct Generation { Generation(uint8_t gen_id, up from_space, up to_space); ~Generation() = default; /** identity of this generation. Generations are numbered from * 0 (youngest) to N (oldest), with N <= c_max_generation **/ uint8_t gen_id_; /** from-space. empty between collection episodes. * During collection holds former to-space **/ up from_space_; /** to-space. New allocations occur here **/ up to_space_; }; #endif struct CollectorConfig { using size_type = std::size_t; #ifdef OBSOLETE // get from arena_config_.header_ /* * alloc header * TTTTTTTTTTTTGGGGGZZZZZZZZZZZZ * < tseq >< size > * * masking * * ..432107654321076543210 bit * * > < .gen_bits * 0..............01111111 gen_mask_unshifted * 0..011111110..........0 gen_mask_shifted * > < gen_shift */ //constexpr std::uint64_t gen_mult() const; constexpr std::uint64_t gen_shift() const; constexpr std::uint64_t gen_mask_unshifted() const; constexpr std::uint64_t gen_mask_shifted() const; //constexpr std::uint64_t tseq_mult() const; constexpr std::uint64_t tseq_shift() const; constexpr std::uint64_t tseq_mask_unshifted() const; constexpr std::uint64_t tseq_mask_shifted() const; #endif generation age2gen(object_age age) const noexcept { return generation(age % n_survive_threshold_); } public: // ----- Instance Variables ----- /** optional name, for diagnostics **/ std::string name_; /** Configuration for collector spaces. * Will have at least {nursery,tenured} x {from,to} spaces. * Not using name_ member. * * REQUIRE: * - arena_config_.store_header_flag_ must be true **/ ArenaConfig arena_config_; /** storage for N object types requires 8*N bytes **/ std::size_t object_types_z_ = 2*1024*1024; /** storage for N object roots requires 8*N bytes **/ std::size_t object_roots_z_ = 16*1024; /** number of bits to represent generation **/ std::uint64_t gen_bits_ = 8; /** number of bits to represent tseq **/ std::uint64_t tseq_bits_ = 24; /** Number of generations. * Must be at least 2. **/ uint32_t n_generation_ = 2; /** Number of promotion steps. * An object that survives this number of collections * advances to the next generation. **/ uint32_t n_survive_threshold_ = 2; /** Trigger garbage collection when to-space allocation for * generation g reaches gc_trigger_v_[g] **/ std::array gc_trigger_v_; /** true -> enable incremental collection. * false -> only do full collection. * * Incremental collection requires mutation logs. **/ bool allow_incremental_gc_ = true; /** If non-zero remember statistics for * the last @p stats_history_z_ collections. **/ uint32_t stats_history_z_ = false; /** true to enable debug logging **/ bool debug_flag_ = false; }; // ----- GCRunState ----- /** @class GCRunState * @brief encapsulate state needed while GC is running **/ struct GCRunState { GCRunState() : gc_upto_{0} {} explicit GCRunState(generation gc_upto); static GCRunState gc_not_running(); static GCRunState gc_upto(generation g); generation gc_upto() const { return gc_upto_; } bool is_running() const { return gc_upto_ > 0; } private: /** running gc collecting all generations gi < gc_upto **/ generation gc_upto_; }; struct DX1CollectorIterator; // ----- DX1Collector ----- struct DX1Collector { using typeseq = xo::facet::typeseq; using size_type = DArena::size_type; using value_type = DArena::value_type; using header_type = DArena::header_type; /** hard max typeseq for collector-registered types **/ static constexpr size_t c_max_typeseq = 4096; /** Create X1 collector instance. **/ explicit DX1Collector(const CollectorConfig & cfg); std::string_view name() const { return config_.name_; } const DArena * get_object_types() const noexcept { return &object_types_; } const DArena * get_roots() const noexcept { return &roots_; } const DArena * get_space(role r, generation g) const noexcept { return space_[r][g]; } DArena * get_space(role r, generation g) noexcept { return space_[r][g]; } DArena * from_space(generation g) noexcept { return get_space(role::from_space(), g); } DArena * to_space(generation g) noexcept { return get_space(role::to_space(), g); } DArena * new_space() noexcept { return to_space(generation{0}); } /** total reserved memory in bytes, across all {role, generation} **/ size_type reserved_total() const noexcept; /** total size in bytes (same as committed_total()) **/ size_type size_total() const noexcept; /** total committed memory in bytes, across all {role, generation} **/ size_type committed_total() const noexcept; /** total available memory in bytes, across all {role, generation} **/ size_type available_total() const noexcept; /** total allocated memory in bytes, across all {role, generation} **/ size_type allocated_total() const noexcept; /** 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; /** return details from last error (will be in gen0 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; /** 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; /** Retreive bookkeeping info for allocation at @p mem. **/ AllocInfo alloc_info(value_type mem) const noexcept; // ----- app memory model ----- /** lookup interface from type sequence * (can use tseq = typeseq::id() for type T) **/ const AGCObject * lookup_type(typeseq tseq) const 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; /** add GC root at @p root_addr, with type @p typeseq **/ void add_gc_root_poly(obj * p_root) noexcept; // ----- collection ----- /** Request immediate collection. * 1. if collection is enabled, immediately collect all generations * up to (but not including) g * 2. may nevertheless escalate to older generations, * depending on collector state. * 3. if collection is currently disabled, * collection will trigger the next time gc is enabled. **/ void request_gc(generation upto) noexcept; /** Execute gc immediately, for all generations < @p upto **/ void execute_gc(generation upto) noexcept; /** Evacuate object at @p *lhs_data to to-space. * Replace original with forwarding pointer to new location **/ void forward_inplace(AGCObject * lhs_iface, void ** lhs_data); /** evacuate object with type @p iface at address @p from_src * to to-space. Return new to-space location. **/ void * shallow_move(const AGCObject * iface, void * from_src); /** true iff {alloc_hdr, object_data} should move for * currently-running collection. * * Require: runstate_.is_running() **/ bool check_move_policy(header_type alloc_hdr, void * object_data) const noexcept; // ----- allocation ----- /** simple allocation. allocate @p z bytes of memory * for an object of type @p t. * New allocs always in gen0 to-space **/ value_type alloc(typeseq t, size_type z) noexcept; /** compound allocation. Allocate @p z bytes of memory * for an object of type @p t. * To be followed immediately by: * 1. zero or more calls to sub_alloc(zi, complete=false), then * 2. exactly one call to sub_alloc(zi, complete=true) * all the allocs in a compound allocation share the same * allocation header. End state is equivalent to a single * allocation with size z + sum(zi). * New allocs always in gen0 to-space **/ value_type super_alloc(typeseq t, size_type z) noexcept; /** sub-allocation with preceding compound allocation. * New allocs always in gen0 to-space **/ value_type sub_alloc(size_type z, bool complete) noexcept; /** Allocate copy of source object at @p src. * Source must be owned by this collector instance. * Copy will have incremented age. **/ value_type alloc_copy(value_type src) noexcept; /** expand gen0 committed size to at least @p z. **/ bool expand(size_type z) noexcept; // ----- iteration ----- /** alloc iterator at begin position **/ DX1CollectorIterator begin() const noexcept; /** alloc iterator at end position * (valid, but cannot be dereferenced) **/ DX1CollectorIterator end() const noexcept; // ----- book-keeping ----- /** reverse to-space and from-space roles for generation g **/ void reverse_roles(generation g) noexcept; /** discard all allocated memory **/ void clear() noexcept; private: /** swap from- and to- roles for all generations < @p upto **/ void swap_roles(generation upto) noexcept; /** copy roots + everything reachable from them, to to-space **/ void copy_roots(generation upto) noexcept; /** move subgraph at @p from_src to to-space. * * Require: runstate_.is_running() **/ void * deep_move(void * from_src, generation upto); public: /** garbage collector configuration **/ CollectorConfig config_; /** current gc state **/ GCRunState runstate_; /** (ab)using arena to get an extensible array of object types. * For each type need to store one (8-byte) IGCObject_Any instance, **/ DArena object_types_; /** gc disabled whenever gc_blocked_ > 0 **/ uint32_t gc_blocked_ = 0; /** if > 0: need gc for all generations < gc_pending_upto_ **/ generation gc_pending_upto_; /** (ab)using arena to get extensible list of root objects. * For each root store one address (type obj*) **/ DArena roots_; /** collector-managed memory here. * - space_[1] is from-space * - space_[0] is to-space * coordinates with role ingc/role.hpp, see also. **/ /** arena objects for collector managed memory * 1:1 with roles, but polarity reverses for each collection **/ std::array 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 space_[c_n_role]; }; } /*namespace mm*/ } /*namespace xo*/ /* end DX1Collector.hpp */