/** @file DX1Collector.cpp * * @author Roland Conybeare, Dec 2025 **/ #include "X1Collector.hpp" #include #include #include #include #include #include "object_age.hpp" #include #include #include #include #include #include // for ::getpagesize() namespace xo { using xo::mm::AAllocator; using xo::facet::TypeRegistry; using xo::facet::typeseq; using xo::facet::with_facet; namespace mm { // ----- GCRunState ----- GCRunState::GCRunState(Mode mode, Generation gc_upto) : mode_{mode}, gc_upto_{gc_upto} {} GCRunState GCRunState::idle() { return GCRunState(Mode::idle, Generation::sentinel()); } GCRunState GCRunState::verify() { return GCRunState(Mode::verify, Generation::sentinel()); } GCRunState GCRunState::gc_upto(Generation g) { return GCRunState(Mode::gc, Generation(g + 1)); } // ----- DX1Collector ----- using size_type = xo::mm::DX1Collector::size_type; DX1Collector::DX1Collector(const X1CollectorConfig & cfg) : config_{cfg} { assert(config_.arena_config_.header_.size_bits_ + config_.arena_config_.header_.age_bits_ + config_.arena_config_.header_.tseq_bits_ <= 64); size_t page_z = getpagesize(); this->_init_object_types(cfg, page_z); this->_init_gc_roots(cfg, page_z); this->_init_mlogs(cfg, page_z); this->_init_space(cfg); } void DX1Collector::_init_object_types(const X1CollectorConfig & cfg, std::size_t page_z) { /* 1MB reserved address space enough for up to 128k distinct types. * In this case don't want to use hugepages since actual #of types * likely << .size/8 */ this->object_types_ = DArena::map(ArenaConfig{.name_ = "x1-object-types", .size_ = cfg.object_types_z_, .hugepage_z_ = page_z, .store_header_flag_ = false}); } void DX1Collector::_init_gc_roots(const X1CollectorConfig & cfg, std::size_t page_z) { this->root_set_ = RootSet::map(ArenaConfig{.name_ = "x1-object-roots", .size_ = cfg.object_roots_z_, .hugepage_z_ = page_z, .store_header_flag_ = false}); } void DX1Collector::_init_mlogs(const X1CollectorConfig & cfg, std::size_t page_z) { for (uint32_t igen = 0, ngen = cfg.n_generation_; igen + 1 < ngen; ++igen) { // special case: no use for mutation log for youngest generation, // so don't trouble to allocate one if (igen + 1 < c_max_generation) { this->mlog_storage_[0][igen] = _make_mlog(igen, 'a', cfg.mutation_log_z_, page_z); this->mlog_storage_[1][igen] = _make_mlog(igen, 'b', cfg.mutation_log_z_, page_z); this->mlog_storage_[2][igen] = _make_mlog(igen, 'c', cfg.mutation_log_z_, page_z); this->mlog_[0][igen] = &mlog_storage_[0][igen]; this->mlog_[1][igen] = &mlog_storage_[1][igen]; this->mlog_[2][igen] = &mlog_storage_[2][igen]; } else { assert(false); } } if (cfg.n_generation_ > 0) { for (uint32_t igen = cfg.n_generation_ - 1; igen + 1 < c_max_generation; ++igen) { this->mlog_[0][igen] = nullptr; this->mlog_[1][igen] = nullptr; this->mlog_[2][igen] = nullptr; } } else { assert(false); } } auto DX1Collector::_make_mlog(uint32_t igen, char tag_char, size_t mlog_z, size_t page_z) -> MutationLog { char buf[40]; snprintf(buf, sizeof(buf), "x1-mlog-G%u-%c", igen, tag_char); return MutationLog::map(ArenaConfig{.name_ = std::string(buf), .size_ = mlog_z, .hugepage_z_ = page_z, .store_header_flag_ = false}); } void DX1Collector::_init_space(const X1CollectorConfig & cfg) { assert(c_n_role == 2); for (uint32_t igen = 0, ngen = cfg.n_generation_; igen < ngen; ++igen) { if (igen < c_max_generation) { { char buf[40]; snprintf(buf, sizeof(buf), "x1-space-G%u-a", igen); this->space_storage_[0][igen] = DArena::map(cfg.arena_config_.with_name(std::string(buf))); } { char buf[40]; snprintf(buf, sizeof(buf), "x1-space-G%u-b", igen); this->space_storage_[1][igen] = DArena::map(cfg.arena_config_.with_name(std::string(buf))); } this->space_[role::to_space()][igen] = &space_storage_[0][igen]; this->space_[role::from_space()][igen] = &space_storage_[1][igen]; } else { assert(false); } } for (uint32_t igen = cfg.n_generation_; igen < c_max_generation; ++igen) { this->space_[role::to_space()][igen] = nullptr; this->space_[role::from_space()][igen] = nullptr; } } void DX1Collector::visit_pools(const MemorySizeVisitor & visitor) const { object_types_.visit_pools(visitor); root_set_.visit_pools(visitor); for (uint32_t j = 0; j < config_.n_generation_; ++j) { for (uint32_t i = 0; i < c_n_role; ++i) { space_storage_[i][j].visit_pools(visitor); } } for (uint32_t j = 0; j + 1 < config_.n_generation_; ++j) { for (uint32_t i = 0; i < c_n_role + 1; ++i) { mlog_storage_[i][j].visit_pools(visitor); } } } bool DX1Collector::contains(role r, const void * addr) const noexcept { return !(this->generation_of(r, addr).is_sentinel()); } bool DX1Collector::contains_allocated(role r, const void * addr) const noexcept { Generation g = this->generation_of(r, addr); if (g.is_sentinel()) return false; return this->get_space(r, g)->contains_allocated(addr); } Generation DX1Collector::generation_of(role r, const void * addr) const noexcept { for (Generation gi{0}; gi < config_.n_generation_; ++gi) { const DArena * arena = get_space(r, gi); if (arena->contains(addr)) return gi; } return Generation::sentinel(); } AllocError DX1Collector::last_error() const noexcept { // TODO: // need to adjust here if runtime errors // encountered during gc. return get_space(role::to_space(), Generation::nursery())->last_error_; } namespace { size_type accumulate_total_aux(const DX1Collector & d, size_t (DArena::* get_stat_fn)() const) noexcept { size_t z1 = (d.object_types_.*get_stat_fn)(); size_t z2 = (d.root_set_.store()->*get_stat_fn)(); size_t z3 = 0; for (role ri : role::all()) { for (Generation gj{0}; gj < d.config_.n_generation_; ++gj) { const DArena * arena = d.get_space(ri, gj); assert(arena); z3 += (arena->*get_stat_fn)(); } } return z1 + z2 + z3; } } size_type DX1Collector::reserved_total() const noexcept { return accumulate_total_aux(*this, &DArena::reserved); } size_type DX1Collector::size_total() const noexcept { return committed_total(); } size_type DX1Collector::committed_total() const noexcept { return accumulate_total_aux(*this, &DArena::committed); } size_type DX1Collector::available_total() const noexcept { return accumulate_total_aux(*this, &DArena::available); } size_type DX1Collector::allocated_total() const noexcept { return accumulate_total_aux(*this, &DArena::allocated); } size_type DX1Collector::header2size(header_type hdr) const noexcept { uint32_t z = config_.arena_config_.header_.size(hdr); return z; } object_age DX1Collector::header2age(header_type hdr) const noexcept { uint32_t age = config_.arena_config_.header_.age(hdr); assert(age < c_max_object_age); return object_age(age); } uint32_t DX1Collector::header2tseq(header_type hdr) const noexcept { uint32_t tseq = config_.arena_config_.header_.tseq(hdr); return tseq; } bool DX1Collector::is_forwarding_header(header_type hdr) const noexcept { /** forwarding pointer encoded as sentinel tseq **/ return config_.arena_config_.header_.is_forwarding_tseq(hdr); } bool DX1Collector::is_type_installed(typeseq tseq) const noexcept { if (object_types_.committed() < sizeof(AGCObject) * (tseq.seqno() + 1)) return false; AGCObject * v = reinterpret_cast(object_types_.lo_); void * vtable = *(void **)&(v[tseq.seqno()]); return (vtable != nullptr); } AllocInfo DX1Collector::alloc_info(value_type mem) const noexcept { for (role ri : role::all()) { for (Generation gj{0}; gj < config_.n_generation_; ++gj) { const DArena * arena = this->get_space(ri, gj); assert(arena); if (arena->contains(mem)) { return arena->alloc_info(mem); } } } // deliberately attempt on nursery to-space, to capture error info + return sentinel return this->get_space(role::to_space(), Generation{0})->alloc_info(mem); } bool DX1Collector::verify_ok() noexcept { // 1. visit space pointers // - verify space_[*] points to space_storage_[*] // - verify mlog_[*] points to mlog_storage_[*] // // 2. visit roots: // for each root, verify that immediate child pointers are in to-space // // 3. scan to-space: // for each object, verify that immediate children are also in to-space // // 4. scan mutation logs: // verify that entries refer to to-space // Each AGCObject impl provides a forward_children() method, // that calls DX1Collector::forward_inplace(iface, &data) // // tactical plan: hijack forward_children. // Add run state so DX1Collector can recognize forward_inplace() // calls made for the purpose of checking child pointers. auto self = this->ref(); GCRunState saved_runstate = runstate_; { this->runstate_ = GCRunState::verify(); this->verify_stats_.clear(); // 2. visit roots for (GCRoot & root_slot : root_set_) { VerifyStats pre = verify_stats_; auto gco = *root_slot.root(); if (gco) { // forward_children is hijacked here to verify // pointer validity. // // Nested control re-enters // - X1Collector::forward_inplace() -> _verify_aux() // gco.forward_children(self); } VerifyStats post = verify_stats_; // assert fail -> root contains ptr to from-space assert(pre.n_from_ == post.n_from_); ++verify_stats_.n_gc_root_; } // 3. scan to-space for each generation for (Generation g(0); g < config_.n_generation_; ++g) { const DArena * space = this->get_space(role::to_space(), g); for (const AllocInfo & info : *space) { if (info.is_forwarding_tseq()) { ++verify_stats_.n_fwd_; } else { typeseq tseq(info.tseq()); const AGCObject * iface = this->lookup_type(tseq); if (iface) { const void * data = info.payload().first; // assembled fop for gc-aware object obj gco(iface, const_cast(data)); // forward_children is hijacked here to verify // child pointer validity. // // Nested control reenters // X1Collector::forward_inplace() // gco.forward_children(self); } else { ++verify_stats_.n_no_iface_; continue; } } } } } // restore run state at end of verify cycle this->runstate_ = saved_runstate; bool ok = verify_stats_.is_ok(); return ok; } const AGCObject * DX1Collector::lookup_type(typeseq tseq) const noexcept { scope log(XO_DEBUG(false)); AGCObject * v = reinterpret_cast(object_types_.lo_); const AGCObject * target = &(v[tseq.seqno()]); if (reinterpret_cast(target) >= object_types_.limit_) { log.retroactively_enable(xtag("tseq", tseq), xtag("tname", TypeRegistry::id2name(tseq))); log(xtag("types.allocated", object_types_.allocated()), xtag("types.committed", object_types_.committed()), xtag("types.lo", object_types_.lo_), xtag("types.limit", object_types_.limit_), xtag("types.hi", object_types_.hi_)); assert(false); return nullptr; } return target; } /* editor bait: register_type */ bool DX1Collector::install_type(const AGCObject & meta) noexcept { typeseq tseq = meta._typeseq(); bool ok = object_types_.expand(sizeof(AGCObject) * (tseq.seqno() + 1)); if (!ok) return false; AGCObject * v = reinterpret_cast(object_types_.lo_); /* explicitly copying vtable pointer here */ std::memcpy((void*)&(v[tseq.seqno()]), (void*)&meta, sizeof(AGCObject)); return true; } void DX1Collector::add_gc_root_poly(obj * p_root) noexcept { root_set_.push_back(GCRoot(p_root)); } void DX1Collector::remove_gc_root_poly(obj * p_root) noexcept { // iterate over roots_, find p_root and drop it (void)p_root; } void DX1Collector::request_gc(Generation upto) noexcept { if (gc_blocked_ > 0) { if (gc_pending_upto_ < upto) { this->gc_pending_upto_ = upto; } /* intend collecting later */ } else { this->execute_gc(upto); } } void DX1Collector::execute_gc(Generation upto) noexcept { scope log(XO_DEBUG(true), xtag("upto", upto)); assert(!runstate_.is_running()); //auto t0 = std::chrono::steady_clock::now(); if (config_.sanitize_flag_) { log && log("step 0a : verify"); this->verify_ok(); } log && log("step 0b : update run state"); this->runstate_ = GCRunState::gc_upto(upto); log && log("step 0c : [STUB] snapshot alloc state"); log && log("step 0d : [STUB] scan for object statistics"); log && log("step 1 : swap from/to roles (now to-space is empty)"); this->swap_roles(upto); log && log(xtag("from_0", get_space(role::from_space(), Generation{0})->lo_), xtag("to_0", get_space(role::to_space(), Generation{0})->lo_)); log && log("step 2a : copy roots"); this->copy_roots(upto); log && log("step 2b : [STUB] copy pinned"); log && log("step 3a : [STUB] run destructors"); log && log("step 3b : [STUB] keep reachable weak pointers"); log && log("step 4 : cleanup"); this->cleanup_phase(upto); if (config_.sanitize_flag_) { log && log("step 4b : verify"); bool ok = this->verify_ok(); log && log(xtag("n-gc-root", verify_stats_.n_gc_root_), xtag("n-ext", verify_stats_.n_ext_), xtag("n-from", verify_stats_.n_from_), xtag("n-to", verify_stats_.n_to_), xtag("n-fwd", verify_stats_.n_fwd_), xtag("n-no-iface", verify_stats_.n_no_iface_)); assert(ok); } } void DX1Collector::swap_roles(Generation upto) noexcept { scope log(XO_DEBUG(true), xtag("upto", upto)); for (Generation g = Generation{0}; g < upto; ++g) { log && log("swap roles", xtag("g", g)); std::swap(space_[role::to_space()][g], space_[role::from_space()][g]); } } void DX1Collector::cleanup_phase(Generation upto) { scope log(XO_DEBUG(true), xtag("upto", upto)); // everything live has been copied out of from-space // -> now set to empty // for (Generation g = Generation{0}; g < upto; ++g) { if (config_.sanitize_flag_) { space_[role::from_space()][g]->scrub(); } space_[role::from_space()][g]->clear(); } this->runstate_ = GCRunState::idle(); } void * DX1Collector::_deep_move_root(obj from_src, Generation upto) { // NOTE: // Some roots are non-gc-owned nodes. // GC must still visit immediate children of these nodes // to move gc-owned children. // This implements virtual root node feature, // intended to mitigate mutation log churn. scope log(XO_DEBUG(config_.debug_flag_)); if (!from_src) return nullptr; bool src_in_from_space = this->contains(role::from_space(), from_src.data()); if (src_in_from_space) { return _deep_move_gc_owned(from_src.data(), upto); } else { // we aren't moving from_src, it's not gc-owned. // However weare moving all its gc-owned children auto self = this->ref(); GCMoveCheckpoint gray_lo_v = this->_snap_move_checkpoint(upto); from_src.forward_children(self); // For each generation g: // traverse objects newer than gray_lo_v[g], to make sure children // are forwarded. Fixpoint reached when gray_lo_v[g] doesn't change. // Remember that forwarding may promote objects to older generation, // so need multiple passes // this->_forward_children_until_fixpoint(upto, gray_lo_v); return from_src.data(); } } void * DX1Collector::_deep_move_interior(void * from_src, Generation upto) { scope log(XO_DEBUG(config_.debug_flag_)); if (!from_src) return nullptr; bool src_in_from_space = this->contains(role::from_space(), from_src); if (!src_in_from_space) return from_src; return _deep_move_gc_owned(from_src, upto); } /* * rules: * - from_src must be in from-space * - object type stored in alloc header * - return value is new location in to-space * * - preserving i/face pointer * - replace destination with forwarding pointer * * EDITOR: gc -> self */ void * DX1Collector::_deep_move_gc_owned(void * from_src, Generation upto) { scope log(XO_DEBUG(config_.debug_flag_)); AllocInfo info = this->alloc_info((std::byte *)from_src); AllocHeader hdr = info.header(); typeseq tseq(info.tseq()); assert(this->contains_allocated(role::from_space(), from_src)); if (is_forwarding_header(hdr)) { /* already forwarded - pickup destination * * Coordinates with forward_inplace() */ log && log("disposition: already forwarded"); return *(void **)from_src; } /* here: object at from_src not already forwarded */ if (!this->check_move_policy(hdr, from_src)) { /* object at from_src is in generation that is not being collected */ log && log("disposition: not moving from_src"); return from_src; } log && log("disposition: move subtree"); /* TODO: AllocIterator pointing to free pointer */ GCMoveCheckpoint gray_lo_v = this->_snap_move_checkpoint(upto); obj alloc(this); const AGCObject * iface = lookup_type(tseq); assert(iface->_has_null_vptr() == false); void * to_dest = this->shallow_move(iface, from_src); this->_forward_children_until_fixpoint(upto, gray_lo_v); log && log(xtag("to_dest", to_dest)); return to_dest; } /*_deep_move_gc_owned*/ auto DX1Collector::_snap_move_checkpoint(Generation upto) -> GCMoveCheckpoint { GCMoveCheckpoint gray_lo_v; for (uint32_t g = 0; g < upto; ++g) { gray_lo_v[g] = this->to_space(Generation{g})->free_; } return gray_lo_v; } void DX1Collector::_forward_children_until_fixpoint(Generation upto, GCMoveCheckpoint gray_lo_v) { scope log(XO_DEBUG(config_.debug_flag_)); /** * To-space: * * to_lo = start of to-space * w,W = white objects. An object x is white if x * + all immediate children of x are in to-space * (also implies this GC cycle put it there) * g,G = grey objects. An object x is gray if it's in to-space, * but possibly has >0 black children * _ = free to-space memory * N = nursery space (generation{0}) * T = tenured space (generation{1}) * * wwwwwwwwwwwwwwwwwwwggggggggggggggggggggg_________________... * ^ ^ ^ * to_lo grey_lo(N) free_ptr(N) * * After moving children of one object, * advancing {nursery_grey_lo, nursery_free_ptr} * * wwwwwwwwwwwwwwwwwwwWWWWgggggggggggggggggGGGGGGGGGGG______... * ^ ^ ^ * to_lo grey_lo(N) free_ptr(N) * * Invariant: * * objects in [to_lo, gray_lo) are white. * all gray objects are in [gray_lo, free_ptr) * memory starting at free_ptr is free. * * deep_move terminates when gray_lo catches up to free_ptr * * Above is simplified. Complication is that GC (including incremental) may * promote objects from nursery (N) to tenured (T) * * So more accurate before/after picture * * N wwwwwwwwwwwwwwwwwwwggggggggggggggggggggg_________________... * ^ ^ ^ * to_lo(N) grey_lo(N) free_ptr(N) * * T wwwwwwwwwwwwwwgggggggggggg_______________________________... * ^ ^ ^ * to_lo(T) grey_lo(T) free_ptr(N) * * After moving children of one object, * advancing {nursery_grey_lo, nursery_free_ptr} * * N wwwwwwwwwwwwwwwwwwwWWWWgggggggggggggggggGGGGGGGGGGG_____... * ^ ^ ^ * to_lo(N) grey_lo(N) free_ptr(N) * * T wwwwwwwwwwwwwwggggggggggggGGGGG_________________________... * ^ ^ ^ * to_lo(T) grey_lo(T) free_ptr(T) * * deep_move terminates when both: * - gray_lo(N) catches up with free_ptr(N) * - gray_lo(T) catches up with free_ptr(T) * **/ std::size_t fixup_work = 0; /* TODO: * - loop here is bad for memory locality * - replace with depth-first traversal */ do { fixup_work = 0; for (Generation g = Generation{0}; g < upto; ++g) { /** object index for this pass **/ size_t i_obj = 0; /* TODO: use AllocIterator here */ while(gray_lo_v[g] < to_space(g)->free_) { AllocHeader * hdr = (AllocHeader *)gray_lo_v[g]; void * src = (hdr + 1); const auto & hdr_cfg = config_.arena_config_.header_; typeseq tseq = typeseq(hdr_cfg.tseq(*hdr)); size_t z = hdr_cfg.size_with_padding(*hdr); log && log("deep_move_gc_owned: fwd to-space children", xtag("g", g), xtag("i_obj", i_obj), xtag("src", src), xtag("tseq", tseq), xtag("tname", TypeRegistry::id2name(tseq)), xtag("z", z)); const AGCObject * iface = this->lookup_type(tseq); assert(iface->_has_null_vptr() == false); auto gc = this->ref(); //obj gc(this); iface->forward_children(src, gc); gray_lo_v[g] = ((std::byte *)src) + z; ++i_obj; ++fixup_work; } } } while (fixup_work > 0); } void DX1Collector::copy_roots(Generation upto) noexcept { scope log(XO_DEBUG(true)); for (RootSet::size_type i = 0, n = root_set_.size(); i < n; ++i) { GCRoot & slot = root_set_[i]; log && log("copy root", xtag("slot.root()", slot.root()), xtag("slot.root()->data_", slot.root()->data_)); void * root_to = this->_deep_move_root(*slot.root(), upto); slot.root()->reset_opaque(root_to); log && log(xtag("slot.root()->data_", slot.root()->data_)); } } void DX1Collector::forward_inplace(AGCObject * lhs_iface, void ** lhs_data) { if (runstate_.is_running()) { // called during collection phase this->_forward_inplace_aux(lhs_iface, lhs_data); } else if (runstate_.is_verify()) { // called during verify_ok this->_verify_aux(lhs_iface, *lhs_data); } else { // should be unreachable assert(false); } } void DX1Collector::_forward_inplace_aux(AGCObject * lhs_iface, void ** lhs_data) { scope log(XO_DEBUG(config_.debug_flag_), xtag("lhs_data", lhs_data), xtag("*lhs_data", *lhs_data)); /* coordinates with DX1Collector::_deep_move() */ (void)lhs_iface; assert(runstate_.is_running()); /* * lhs obj * | +---------+ +---+-+----+ * \--->| .iface | | T |G|size| header * +---------+ object_data +---+-+----+ * | .data x----------------->| alloc | * +---------+ | data | * | for | * | instance | * | ... | * +----------+ */ void * object_data = (std::byte *)*lhs_data; if (!this->contains(role::from_space(), object_data)) { /* *lhs_data either: * 1. already in to-space * 2. not in GC-allocated space at all * (small number of niche examples of this) * * It's important we recognize case (2) up front. * Since not allocated from GC, they don't have * an alloc-header. */ log && log("disposition: not in from-space"); return; } log && log("disposition: in from-space"); /** NOTE: for form's sake: * lookup actual arena that * allocated object data. * Only using this to get alloc header **/ DArena * some_arena = this->from_space(Generation(0)); DArena::header_type * p_header = some_arena->obj2hdr(object_data); DArena::header_type alloc_hdr = *p_header; /* recover allocation size */ std::size_t alloc_z = some_arena->config_.header_.size_with_padding(alloc_hdr); if (log) { log(xtag("some_arena.lo", some_arena->lo_), xtag("p_header", p_header), xtag("alloc_z", alloc_z)); AllocInfo info = this->alloc_info((std::byte *)object_data); log(xtag("tseq", info.tseq()), xtag("tname", TypeRegistry::id2name(typeseq(info.tseq()))), xtag("is_forwarding_tseq", info.is_forwarding_tseq()), xtag("age", info.age()), xtag("size", info.size())); } /* need to be able to fit forwarding pointer * in place of forwarded object. * * This is guaranteed anyway, by alignment rules */ assert(alloc_z >= sizeof(uintptr_t)); if (this->is_forwarding_header(alloc_hdr)) { /* *lhs_data already refers to a forwarding pointer */ /* * lhs obj (from-space) * | +---------+ +---+-+----+ * \--->| .iface | |FWD|G|size| alloc_hdr * +---------+ object_data +---+-+----+ * | .data x----------------->| x--------\ * +---------+ | | | dest * | | | * +----------+ | * | * (to-space) | * +---+-+----+ | * |TSQ|G|size| | * +---+-+----+ | * | | <-/ * | | * | | * +----------+ */ void * dest = *(void**)object_data; *lhs_data = dest; /* * lhs obj * | +---------+ * \--->| .iface | * +---------+ * | .data x------------\ * +---------+ | * | dest * | * | * | (to-space) * | +---+-+----+ * | |TSQ|G|size| * | +---+-+----+ * \---> | | * | | * | | * +----------+ */ if (log) { log("lhs_data already forwarded", xtag("dest", dest)); AllocInfo info = this->alloc_info((std::byte *)dest); log(xtag("tseq", info.tseq()), xtag("tname", TypeRegistry::id2name(typeseq(info.tseq()))), xtag("age", info.age()), xtag("size", info.size())); } } else if (this->check_move_policy(alloc_hdr, object_data)) { /* copy object *lhs + replace with forwarding pointer */ log && log("forward object now"); /* * lhs obj (from-space) * | +---------+ +---+-+----+ * \--->| .iface | |TSQ|G|size| alloc_hdr * +---------+ object_data +---+-+----+ * | .data x----------------> | | * +---------+ | | * | | * +----------+ */ *lhs_data = this->shallow_move(lhs_iface, *lhs_data); /* * lhs obj (from-space) * | +---------+ +---+-+----+ * \--->| .iface | |FWD|G|SIZE| * +---------+ +---+-+----+ * | .data x------------\ | x--------\ * +---------+ | | | | * | | | | * dest | +----------+ | * | | * | (to-space) | * | +---+-+----+ | * | |TSQ|G|size| | * | +---+-+----+ | * \---> | | <-/ * | | * | | * +----------+ */ } else { log && log("object not eligible/required to forward"); /* object doesn't need to move. * e.g. incremental collection + object is tenured */ } } /*_forward_inplace*/ void DX1Collector::_verify_aux(AGCObject * iface, void * data) { scope log(XO_DEBUG(config_.debug_flag_), xtag("data", data)); (void)iface; (void)data; Generation g1 = this->generation_of(role::to_space(), data); if (g1.is_sentinel()) { assert(this->contains(role::to_space(), data) == false); Generation g2 = this->generation_of(role::from_space(), data); if (!g2.is_sentinel()) { // verify failure - live pointer still refers to from-space ++(verify_stats_.n_from_); } else { ++(verify_stats_.n_ext_); } } else { assert(this->contains(role::to_space(), data)); ++(verify_stats_.n_to_); } } void * DX1Collector::shallow_move(const AGCObject * iface, void * from_src) { scope log(XO_DEBUG(config_.debug_flag_)); AllocInfo info = this->alloc_info((std::byte *)from_src); obj alloc(this); void * to_dest = iface->shallow_copy(from_src, alloc); log && log(xtag("from_src", from_src), xtag("to_dest", to_dest)); log && log(xtag("tseq", info.tseq()), xtag("tname", TypeRegistry::id2name(typeseq(info.tseq()))), xtag("age", info.age()), xtag("size", info.size())); if(to_dest == from_src) { assert(false); } else { *(const_cast(info.p_header_)) = AllocHeader(config_ .arena_config_ .header_ .mark_forwarding_tseq(*info.p_header_)); *(void **)from_src = to_dest; } return to_dest; } bool DX1Collector::check_move_policy(header_type alloc_hdr, void * object_data) const noexcept { (void)object_data; // when gc is moving objects, to- and from- spaces have been // reversed: forwarding pointers are located in from-space and // refer to to-space. object_age age = this->header2age(alloc_hdr); Generation g = config_.age2gen(age); assert(runstate_.is_running()); return (g < runstate_.gc_upto()); } auto DX1Collector::alloc(typeseq t, size_type z) noexcept -> value_type { return with_facet::mkobj(new_space()).alloc(t, z); } auto DX1Collector::super_alloc(typeseq t, size_type z) noexcept -> value_type { return with_facet::mkobj(new_space()).super_alloc(t, z); } auto DX1Collector::sub_alloc(size_type z, bool complete) noexcept -> value_type { return with_facet::mkobj(new_space()).sub_alloc(z, complete); } auto DX1Collector::alloc_copy(value_type src) noexcept -> value_type { return with_facet::mkobj(new_space()).alloc_copy(src); } bool DX1Collector::expand(size_type z) noexcept { if (with_facet::mkobj(to_space(Generation{0})).expand(z)) return with_facet::mkobj(from_space(Generation{0})).expand(z); return false; } // editor bait: write barrier void DX1Collector::assign_member(void * parent, obj * p_lhs, obj rhs) { scope log(XO_DEBUG(config_.debug_flag_), xtag("parent", parent), xtag("lhs", p_lhs), xtag("rhs", rhs.data())); // ++ stats.n_mutation_; *p_lhs = rhs; if (runstate_.is_running()) { // for removal of all doubt: // don't log mutations during GC cycle return; } if (!config_.allow_incremental_gc_) { // only need to log mutations when incremental gc is enabled return; } // logging policy depends on: // 1. generation of lhs // 2. generation of rhs Generation src_g = this->generation_of(role::to_space(), p_lhs); if (src_g.is_sentinel()) { // only need mlog entries for gc-owned pointers. // In this case pointer does not originate in gc-owned space return; } Generation dest_g = this->generation_of(role::to_space(), rhs.data()); if (dest_g.is_sentinel()) { // similarly, don't need mlog entry to non-gc-owned destination return; } if (src_g < dest_g) { // young-to-old pointers don't need to be remembered, // since a GC cycle that collects the old generation is guarnatted // to also collect the young generation. return; } if (src_g == dest_g) { // for pointers within the same generation, need to log // if source is older than destination. const DArena * arena = this->get_space(role::to_space(), src_g); const DArena::header_type * src_hdr = arena->obj2hdr(parent); const DArena::header_type * dest_hdr = arena->obj2hdr(rhs.data()); assert(src_hdr && dest_hdr); if (header2age(*src_hdr) <= header2age(*dest_hdr)) { // source and destination have the same age; // therefore are always collected on the same set of GC cycles // -> no need to remember separately. return; } else { // even though {src,dest} belong to the same generation: // source will be eligible for promotion before destination. // At that point pointer would become a cross-generational pointer, // so need to track it now. log && log("xage ptr -> must log"); } } else { log && log("xgen ptr -> must log"); } // control here: we have an older->younger pointer, need to log it // mlog keyed by generation in which pointer _destination_ resides: // collection that moves destination generation around needs to also // update pointers such as this one // MutationLog * mlog = this->mlog_[role::to_space()][dest_g]; mlog->push_back(MutationLogEntry(parent, reinterpret_cast(&(p_lhs->data_)), rhs)); } DX1CollectorIterator DX1Collector::begin() const noexcept { scope log(XO_DEBUG(false)); const DArena * arena = get_space(role::to_space(), Generation{0}); return DX1CollectorIterator(this, Generation{0}, Generation{config_.n_generation_}, arena->begin(), arena->end()); } DX1CollectorIterator DX1Collector::end() const noexcept { scope log(XO_DEBUG(false)); Generation gen_hi = Generation{config_.n_generation_}; /** valid iterator for end points to end of last DArena. * otherwise will interfere with working compare * (since invalid iterators are incomparable) **/ const DArena * arena = get_space(role::to_space(), Generation(config_.n_generation_ - 1)); DArenaIterator arena_end = arena->end(); return DX1CollectorIterator(this, gen_hi, gen_hi, arena_end, arena_end); } void DX1Collector::reverse_roles(Generation g) noexcept { assert(g < config_.n_generation_); std::swap(space_[role::from_space()][g], space_[role::to_space()][g]); } void DX1Collector::clear() noexcept { for (role ri : role::all()) { for (Generation gj{0}; gj < config_.n_generation_; ++gj) { DArena * arena = this->get_space(ri, gj); assert(arena); arena->clear(); } } } } /*namespace mm*/ } /*namespace xo*/ /* end DX1Collector.cpp */