xo-gc: mutation log properly handled during collection cycle.
NOT TESTED
This commit is contained in:
parent
f85d78dfdc
commit
41285fc3ed
4 changed files with 361 additions and 5 deletions
|
|
@ -129,6 +129,31 @@ namespace xo {
|
|||
alignas(AGCObject) std::byte iface_[sizeof(AGCObject)];
|
||||
};
|
||||
|
||||
/** @brief statistics from mlog forwarding **/
|
||||
class MutationLogStatistics {
|
||||
public:
|
||||
MutationLogStatistics() = default;
|
||||
|
||||
MutationLogStatistics & operator+=(const MutationLogStatistics & x) {
|
||||
n_stale_ += x.n_stale_;
|
||||
n_live_parent_ += x.n_live_parent_;
|
||||
n_rescue_ += x.n_rescue_;
|
||||
n_triage_ += x.n_triage_;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
public:
|
||||
/** count superseded mlog entries **/
|
||||
std::size_t n_stale_ = 0;
|
||||
/** count live parents encountered during mlog scan **/
|
||||
std::size_t n_live_parent_ = 0;
|
||||
/** count child subgraphs rescued during mlog scan **/
|
||||
std::size_t n_rescue_ = 0;
|
||||
/** count triaged mlog entries **/
|
||||
std::size_t n_triage_ = 0;
|
||||
};
|
||||
|
||||
/** @brief info collected during a @ref DX1Collector::verify_ok call
|
||||
*
|
||||
**/
|
||||
|
|
@ -445,6 +470,44 @@ namespace xo {
|
|||
/** copy roots + everything reachable from them, to to-space **/
|
||||
void copy_roots(Generation upto) noexcept;
|
||||
|
||||
/** cureate new mutation log after copying roots **/
|
||||
void forward_mutation_log(Generation upto);
|
||||
|
||||
/** Perform one pass over contents of @p *from_mlog for generation @p gen.
|
||||
* @p *from_mlog contains all {xgen,xage} pointers that target generation @p gen.
|
||||
* Surviving mlog entries are moved to either @p *to_mlog or @p *triage_mlog,
|
||||
* (generation < @p upto being collected this cycle).
|
||||
*
|
||||
* Each mlog entry gets one of the following outcomes.
|
||||
* 1. skip. mlog entry has been superseded by another mut at target site.
|
||||
* 2. keep. mlog entry is live. destination has been evacuated,
|
||||
* so source must be updated as well.
|
||||
* 3. triage. source of incoming object belongs to a generation that was collected,
|
||||
* and has not been evacuated. Although appears to be garbage, it may
|
||||
* be live after all if reachable from the destination of some other
|
||||
* mlog entry in @p *to_mlog. Store these mlog entries in @p *triage_mlog.
|
||||
*
|
||||
* @return number of mlog entries moved, whether to @p *to_mlog or @p *triage_mlog.
|
||||
**/
|
||||
MutationLogStatistics _forward_mutation_log_phase(Generation upto,
|
||||
Generation gen,
|
||||
MutationLog * from_mlog,
|
||||
MutationLog * to_mlog,
|
||||
MutationLog * triage_mlog);
|
||||
|
||||
MutationLogStatistics _preserve_child_of_live_parent(Generation upto,
|
||||
Generation parent_gen,
|
||||
const MutationLogEntry & from_entry,
|
||||
MutationLog * keep_mlog);
|
||||
|
||||
/** helper function to decide whether to keep a mutation log entry
|
||||
* @return true iff mlog entry appended to @p keep_mlog
|
||||
**/
|
||||
bool _check_keep_mutation_aux(const MutationLogEntry & from_entry,
|
||||
Generation parent_gen_to,
|
||||
void * child_to,
|
||||
MutationLog * keep_mlog);
|
||||
|
||||
/** cleanup after gc **/
|
||||
void cleanup_phase(Generation upto);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,12 +28,16 @@ namespace xo {
|
|||
using AGCObject = xo::mm::AGCObject;
|
||||
|
||||
public:
|
||||
MutationLogEntry() = default;
|
||||
MutationLogEntry(void * parent, void ** p_data, obj<AGCObject> snap);
|
||||
|
||||
void * parent() const { return parent_; }
|
||||
void ** p_data() const { return p_data_; }
|
||||
obj<AGCObject> snap() const { return snap_; }
|
||||
|
||||
/** true if child pointer has been altered since this mlog entry created **/
|
||||
bool is_superseded() const noexcept { return *p_data_ != snap_.data(); }
|
||||
|
||||
private:
|
||||
/** address of object containing logged mutation **/
|
||||
void * parent_ = nullptr;
|
||||
|
|
@ -41,7 +45,7 @@ namespace xo {
|
|||
* driving this log entry.
|
||||
**/
|
||||
void ** p_data_ = nullptr;
|
||||
/** AGCObject i/face pointer, asof assignment responsible for this log entry.
|
||||
/** AGCObject child pointer, asof assignment responsible for this log entry.
|
||||
* If *p_data_ matches snap_.data(), then AGCObject interface is snap_.iface().
|
||||
* Otherwise log entry has been superseded by another assignment.
|
||||
**/
|
||||
|
|
|
|||
|
|
@ -40,6 +40,15 @@ namespace xo {
|
|||
return Generation(age % n_survive_threshold_);
|
||||
}
|
||||
|
||||
/** age threshold for promotion to generation @p g **/
|
||||
uint32_t promotion_threshold(Generation g) const noexcept {
|
||||
// TODO: may consider replacing with table-lookup
|
||||
// Require: if two distinct ages promote to some gen g at the same time,
|
||||
// then they also promote to gen g+k at the same time for all k>0.
|
||||
|
||||
return g * n_survive_threshold_;
|
||||
}
|
||||
|
||||
public:
|
||||
// ----- Instance Variables -----
|
||||
|
||||
|
|
|
|||
|
|
@ -949,14 +949,18 @@ namespace xo {
|
|||
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");
|
||||
log && log("step 3 : [STUB] forward mutation log");
|
||||
this->forward_mutation_log(upto);
|
||||
|
||||
log && log("step 4a : [STUB] run destructors");
|
||||
log && log("step 4b : [STUB] keep reachable weak pointers");
|
||||
|
||||
log && log("step 5 : cleanup");
|
||||
this->cleanup_phase(upto);
|
||||
|
||||
if (config_.sanitize_flag_) {
|
||||
log && log("step 4b : verify");
|
||||
log && log("step 5b : verify");
|
||||
bool ok = this->verify_ok();
|
||||
|
||||
log && log(xtag("n-gc-root", verify_stats_.n_gc_root_),
|
||||
|
|
@ -983,6 +987,282 @@ namespace xo {
|
|||
log && log("swap roles", xtag("g", g));
|
||||
|
||||
std::swap(space_[role::to_space()][g], space_[role::from_space()][g]);
|
||||
std::swap(mlog_[role::to_space()][g], mlog_[role::from_space()][g]);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DX1Collector::forward_mutation_log(Generation upto)
|
||||
{
|
||||
/** non-zero if at least one object was rescued (from any generation)
|
||||
* by mutation log scan
|
||||
**/
|
||||
std::size_t work = 0;
|
||||
|
||||
do {
|
||||
// on 1st iteration, for all generations:
|
||||
// - to_mlog, triage_mlog are empty
|
||||
|
||||
for (Generation child_gen{0}; child_gen + 2 < config_.n_generation_; ++child_gen) {
|
||||
|
||||
MutationLog * from_mlog = this->mlog_[role::from_space()][child_gen];
|
||||
|
||||
if (!from_mlog->empty()) {
|
||||
MutationLog * to_mlog = this->mlog_[role::to_space()][child_gen];
|
||||
MutationLog * triage_mlog = this->mlog_[c_n_role][child_gen];
|
||||
|
||||
auto stats = this->_forward_mutation_log_phase(upto,
|
||||
child_gen,
|
||||
from_mlog,
|
||||
to_mlog,
|
||||
triage_mlog);
|
||||
|
||||
from_mlog->clear();
|
||||
|
||||
// {from_mlog, triage_mlog} reverse roles
|
||||
|
||||
std::swap(this->mlog_[role::from_space()][child_gen],
|
||||
this->mlog_[c_n_role][child_gen]);
|
||||
|
||||
work += stats.n_rescue_;
|
||||
}
|
||||
}
|
||||
} while (work > 0);
|
||||
|
||||
// here: reached fixpoints, any remaining triaged mlogs can be discarded
|
||||
for (Generation child_gen{0}; child_gen + 2 < config_.n_generation_; ++child_gen) {
|
||||
MutationLog * triage_mlog = this->mlog_[c_n_role][child_gen];
|
||||
|
||||
triage_mlog->clear();
|
||||
}
|
||||
}
|
||||
|
||||
MutationLogStatistics
|
||||
DX1Collector::_forward_mutation_log_phase(Generation upto,
|
||||
Generation child_gen,
|
||||
MutationLog * from_mlog,
|
||||
MutationLog * keep_mlog,
|
||||
MutationLog * triage_mlog)
|
||||
{
|
||||
scope log(XO_DEBUG(config_.debug_flag_),
|
||||
xtag("child_gen", child_gen),
|
||||
xtag("mlog.size", from_mlog->size()));
|
||||
|
||||
/* categorize each mlog entry based on combination of {src, dest}.
|
||||
* In each case we care about {gen, age} of {src, dest}
|
||||
* objects.
|
||||
*
|
||||
* Enough cases to deserve a table:
|
||||
*
|
||||
* Legend:
|
||||
* - P : parent object
|
||||
* - P' : parent object after this gc phase
|
||||
* - g(P) : generation of parent P.
|
||||
* '+' if gen > child_gen (parent gen not collected this cycle)
|
||||
* - age(P) : age of parent P.
|
||||
*
|
||||
* - C : child object
|
||||
* - C' : child object after this gc phase
|
||||
* - g(C) : generation of child C.
|
||||
* - age(C) : age of child C.
|
||||
*
|
||||
* - 0 : *from_mlog targets this object's generation.
|
||||
* object not eligible for promotion.
|
||||
* Write self* for objects eligible that promote
|
||||
* if they survive this gc cycle.
|
||||
* Write + for 'any generation senior to target'
|
||||
* - 1 : *from_mlog target this object's generation;
|
||||
* object promotes if it survives
|
||||
*
|
||||
* - role : 'to' this phase evacuated
|
||||
* (or in generation not eligible for collection)
|
||||
* 'fr' otherwise
|
||||
*
|
||||
* | mlog | par | | | mlog | upd
|
||||
* case | cur | g(P) | P C | C' | action | P | move
|
||||
* -------+------+-------+--------------+------+---------+--------+-----
|
||||
* MLOG0 | no | | | | discard | | -
|
||||
* | | | | | | |
|
||||
* MLOG1 | yes | * | to:+ fwd:* | to | keep | P->C' | -
|
||||
* MLOG2 | yes | | fr:0 | to | keep | P->C' | C->to
|
||||
* MLOG3 | yes | * | fwd:* - | to | update | P'->C' | -
|
||||
* MLOG4 | yes | | fr:* - | - | triage | - | -
|
||||
|
||||
* notes:
|
||||
* MLOG1 : child C already forwarded (whether or not promoted)
|
||||
* MLOG2 : child C survives (and perhaps promoted).
|
||||
* kept alive by parent in more-senior generation
|
||||
* MLOG3 : parent has been forwarded.
|
||||
* update mlog entry for new parent location
|
||||
* MLOG4 : parent provisionally garbage. triage mlog entry until
|
||||
* definite outcome.
|
||||
*/
|
||||
|
||||
MutationLogStatistics counters;
|
||||
// index of current mlog entry during evac
|
||||
std::uint32_t i_from = 0;
|
||||
|
||||
for (MutationLogEntry & from_entry : *from_mlog) {
|
||||
if (log) {
|
||||
log(xtag("i_from", i_from));
|
||||
}
|
||||
|
||||
if (from_entry.is_superseded()) {
|
||||
// there must be a second mlog entry that refers to
|
||||
// the new child. Rely on that second entry,
|
||||
// skipping this one.
|
||||
|
||||
// [MLOG0] obsolete mutation -> skip
|
||||
++counters.n_stale_;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* here: mlog current */
|
||||
|
||||
Generation parent_gen_to = this->generation_of(role::to_space(),
|
||||
from_entry.parent());
|
||||
|
||||
if (parent_gen_to.is_sentinel()) {
|
||||
void * parent_fr = *from_entry.p_data();
|
||||
|
||||
AllocInfo parent_info = this->alloc_info((std::byte *)parent_fr);
|
||||
|
||||
if (parent_info.is_forwarding_tseq()) {
|
||||
/* [MLOG3] */
|
||||
|
||||
++counters.n_live_parent_;
|
||||
|
||||
// new parent location in to-space
|
||||
// TODO: method on AllocInfo to streamline this
|
||||
void * parent_to = *(void **)parent_fr;
|
||||
|
||||
parent_gen_to = this->generation_of(role::to_space(),
|
||||
parent_to);
|
||||
parent_info = this->alloc_info((std::byte *)parent_to);
|
||||
|
||||
assert(!parent_gen_to.sentinel());
|
||||
|
||||
// Since parent already forwarded, we don't have to preserve child
|
||||
// or update parent object.
|
||||
//
|
||||
// Do need to replace mlog entry to reflect new parent location.
|
||||
|
||||
std::size_t offset
|
||||
= ((std::byte *)from_entry.p_data()
|
||||
- (std::byte *)from_entry.parent());
|
||||
|
||||
void ** p_data_to = (void **)((std::byte *)(parent_to) + offset);
|
||||
void * child_to = *p_data_to;
|
||||
|
||||
MutationLogEntry to_entry(parent_to, p_data_to, from_entry.snap());
|
||||
|
||||
this->_check_keep_mutation_aux(to_entry,
|
||||
parent_gen_to,
|
||||
child_to,
|
||||
keep_mlog);
|
||||
|
||||
|
||||
} else {
|
||||
++counters.n_triage_;
|
||||
|
||||
// parent hasn't been collected and may be garbage.
|
||||
// However this is only provisional, since
|
||||
// parent could turn out to be reachable via some other mutation.
|
||||
|
||||
triage_mlog->push_back(from_entry);
|
||||
}
|
||||
} else {
|
||||
/* [MLOG1, MLOG2] */
|
||||
|
||||
counters += this->_preserve_child_of_live_parent(upto,
|
||||
parent_gen_to,
|
||||
from_entry,
|
||||
keep_mlog);
|
||||
}
|
||||
}
|
||||
|
||||
return counters;
|
||||
}
|
||||
|
||||
MutationLogStatistics
|
||||
DX1Collector::_preserve_child_of_live_parent(Generation upto,
|
||||
Generation parent_gen,
|
||||
const MutationLogEntry & from_entry,
|
||||
MutationLog * keep_mlog)
|
||||
{
|
||||
void * child_fr = *from_entry.p_data();
|
||||
AllocInfo child_info = this->alloc_info((std::byte *)(child_fr));
|
||||
|
||||
MutationLogStatistics counters;
|
||||
|
||||
// if child collected: new child location in to-space
|
||||
void * child_to = nullptr;
|
||||
|
||||
// parent is alive: gc must ensure child remains alive
|
||||
|
||||
++counters.n_live_parent_;
|
||||
|
||||
// Parent already recognized as alive. Either not subject to collection
|
||||
// or already evacuated.
|
||||
// (+ remember this need not be 1st pass over mlog entries)
|
||||
|
||||
if (child_info.is_forwarding_tseq()) {
|
||||
// [MLOG1]
|
||||
|
||||
// child already forwarded.
|
||||
// TODO: make this a method on AllocInfo
|
||||
child_to = *(void **)child_fr;
|
||||
|
||||
// assigning through address of P->C pointer
|
||||
// also makes mlog entry current
|
||||
|
||||
} else {
|
||||
// [MLOG2]
|
||||
|
||||
++counters.n_rescue_;
|
||||
|
||||
child_to = this->_deep_move_interior(child_fr, upto);
|
||||
|
||||
// update child pointer in parent object
|
||||
*from_entry.p_data() = child_to;
|
||||
}
|
||||
|
||||
// child_to generation in {gen, gen+1}
|
||||
|
||||
this->_check_keep_mutation_aux(from_entry, parent_gen, child_to, keep_mlog);
|
||||
|
||||
return counters;
|
||||
}
|
||||
|
||||
bool
|
||||
DX1Collector::_check_keep_mutation_aux(const MutationLogEntry & from_entry,
|
||||
Generation parent_gen_to,
|
||||
void * child_to,
|
||||
MutationLog * keep_mlog)
|
||||
{
|
||||
Generation child_gen_to
|
||||
= this->generation_of(role::to_space(), child_to);
|
||||
|
||||
bool need_mlog_entry
|
||||
= ((child_gen_to + 1 < config_.n_generation_)
|
||||
&& (config_.promotion_threshold(parent_gen_to)
|
||||
> config_.promotion_threshold(child_gen_to)));
|
||||
|
||||
if (need_mlog_entry) {
|
||||
// 1. P->C pointer is still cross-age (xage), and
|
||||
// 2. this matters; in future P will promote before C
|
||||
//
|
||||
// Need to keep entry because parent will be eligible for promotion
|
||||
// before child
|
||||
|
||||
keep_mlog->push_back(from_entry);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
// child now in final generation,
|
||||
// no longer need to track incoming mutations.
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue