xo-ordinaltree: expand unittest + debug logging
This commit is contained in:
parent
b6ccde3ddc
commit
bd8ca68e7c
4 changed files with 106 additions and 14 deletions
|
|
@ -107,7 +107,7 @@ namespace xo {
|
||||||
bool is_dead() const { return false; }
|
bool is_dead() const { return false; }
|
||||||
|
|
||||||
MutationLogEntry update_parent_moved(IObject * parent_to) const;
|
MutationLogEntry update_parent_moved(IObject * parent_to) const;
|
||||||
void fixup_parent_child_moved(IObject * child_to) { *lhs_ = child_to; }
|
void fixup_parent_child_moved(IObject * child_to);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
IObject * parent_ = nullptr;
|
IObject * parent_ = nullptr;
|
||||||
|
|
@ -341,7 +341,7 @@ namespace xo {
|
||||||
* is recorded in mutation log,
|
* is recorded in mutation log,
|
||||||
* given an object @p parent that contains object pointer @p lhs
|
* given an object @p parent that contains object pointer @p lhs
|
||||||
**/
|
**/
|
||||||
virtual bool check_write_barrier(IObject * parent, IObject ** lhs, bool may_throw) const final;
|
virtual bool check_write_barrier(const void * parent, const void * const * lhs, bool may_throw) const final;
|
||||||
|
|
||||||
virtual std::byte * alloc(std::size_t z) final override;
|
virtual std::byte * alloc(std::size_t z) final override;
|
||||||
virtual std::byte * alloc_gc_copy(std::size_t z, const void * src) final override;
|
virtual std::byte * alloc_gc_copy(std::size_t z, const void * src) final override;
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,12 @@ namespace xo {
|
||||||
reinterpret_cast<IObject **>(lhs_to));
|
reinterpret_cast<IObject **>(lhs_to));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MutationLogEntry::fixup_parent_child_moved(IObject * child_to)
|
||||||
|
{
|
||||||
|
*(this->lhs_) = child_to;
|
||||||
|
}
|
||||||
|
|
||||||
GC::GC(const Config & config)
|
GC::GC(const Config & config)
|
||||||
: config_{config}
|
: config_{config}
|
||||||
{
|
{
|
||||||
|
|
@ -508,12 +514,17 @@ namespace xo {
|
||||||
|
|
||||||
assert(retval);
|
assert(retval);
|
||||||
|
|
||||||
|
log && log(xtag("retval", retval));
|
||||||
|
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
GC::assign_member(IObject * parent, IObject ** lhs, IObject * rhs)
|
GC::assign_member(IObject * parent, IObject ** lhs, IObject * rhs)
|
||||||
{
|
{
|
||||||
|
scope log(XO_DEBUG(config_.debug_flag_),
|
||||||
|
xtag("parent", parent), xtag("lhs", lhs), xtag("rhs", rhs));
|
||||||
|
|
||||||
++gc_statistics_.n_mutation_;
|
++gc_statistics_.n_mutation_;
|
||||||
|
|
||||||
*lhs = rhs;
|
*lhs = rhs;
|
||||||
|
|
@ -532,23 +543,28 @@ namespace xo {
|
||||||
{
|
{
|
||||||
case generation_result::tenured:
|
case generation_result::tenured:
|
||||||
/* only need to log mutations that create tenured->nursery pointers */
|
/* only need to log mutations that create tenured->nursery pointers */
|
||||||
|
log && log(xtag("act", "any->T no log"));
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case generation_result::nursery:
|
case generation_result::nursery:
|
||||||
switch (tospace_generation_of(parent)) {
|
switch (tospace_generation_of(parent)) {
|
||||||
case generation_result::nursery:
|
case generation_result::nursery:
|
||||||
if (is_before_checkpoint(parent)) {
|
if (is_before_checkpoint(parent)) {
|
||||||
|
log && log(xtag("act", "N1->N0 must mlog"));
|
||||||
|
|
||||||
// N1->N0, so must log
|
// N1->N0, so must log
|
||||||
this->mutation_log_[role2int(role::to_space)]->push_back(MutationLogEntry(parent, lhs));
|
this->mutation_log_[role2int(role::to_space)]->push_back(MutationLogEntry(parent, lhs));
|
||||||
++(this->gc_statistics_.n_logged_mutation_);
|
++(this->gc_statistics_.n_logged_mutation_);
|
||||||
++(this->gc_statistics_.n_xckp_mutation_);
|
++(this->gc_statistics_.n_xckp_mutation_);
|
||||||
} else {
|
} else {
|
||||||
// parent in N0, not an xckp mutation
|
// parent in N0, not an xckp mutation
|
||||||
return;
|
log && log(xtag("act", "N0->any no long"));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case generation_result::tenured:
|
case generation_result::tenured:
|
||||||
// T->N, so must log
|
// T->N, so must log
|
||||||
|
log && log(xtag("act", "T->N must mlog"));
|
||||||
|
|
||||||
this->mutation_log_[role2int(role::to_space)]->push_back(MutationLogEntry(parent, lhs));
|
this->mutation_log_[role2int(role::to_space)]->push_back(MutationLogEntry(parent, lhs));
|
||||||
++(this->gc_statistics_.n_logged_mutation_);
|
++(this->gc_statistics_.n_logged_mutation_);
|
||||||
++(this->gc_statistics_.n_xgen_mutation_);
|
++(this->gc_statistics_.n_xgen_mutation_);
|
||||||
|
|
@ -556,11 +572,13 @@ namespace xo {
|
||||||
case generation_result::not_found:
|
case generation_result::not_found:
|
||||||
// parent is global
|
// parent is global
|
||||||
// This may be ok (provided lhs is a gc root)
|
// This may be ok (provided lhs is a gc root)
|
||||||
|
log && log(xtag("act", "root->any no log"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case generation_result::not_found:
|
case generation_result::not_found:
|
||||||
|
log && log(xtag("act", "any->root no log"));
|
||||||
|
|
||||||
// child is global;
|
// child is global;
|
||||||
// logging not required
|
// logging not required
|
||||||
|
|
@ -571,6 +589,8 @@ namespace xo {
|
||||||
void
|
void
|
||||||
GC::forward_inplace(IObject ** lhs)
|
GC::forward_inplace(IObject ** lhs)
|
||||||
{
|
{
|
||||||
|
scope log(XO_DEBUG(config_.debug_flag_), xtag("lhs", lhs));
|
||||||
|
|
||||||
Object::_forward_inplace(lhs, this);
|
Object::_forward_inplace(lhs, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -588,10 +608,13 @@ namespace xo {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
GC::check_write_barrier(IObject * parent,
|
GC::check_write_barrier(const void * parent,
|
||||||
IObject ** lhs,
|
const void * const * lhs,
|
||||||
bool may_throw_flag) const
|
bool may_throw_flag) const
|
||||||
{
|
{
|
||||||
|
scope log(XO_DEBUG(config_.debug_flag_),
|
||||||
|
xtag("P", parent), xtag("L", lhs));
|
||||||
|
|
||||||
if (!this->contains(parent)) {
|
if (!this->contains(parent)) {
|
||||||
if (may_throw_flag) {
|
if (may_throw_flag) {
|
||||||
throw std::runtime_error(tostr("GC::check_write_barrier",
|
throw std::runtime_error(tostr("GC::check_write_barrier",
|
||||||
|
|
@ -601,10 +624,11 @@ namespace xo {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef NOPE // don't want to assume IObject*
|
||||||
std::size_t parent_z = parent->_shallow_size();
|
std::size_t parent_z = parent->_shallow_size();
|
||||||
|
|
||||||
std::byte * parent_addr = reinterpret_cast<std::byte *>(parent);
|
const std::byte * parent_addr = reinterpret_cast<const std::byte *>(parent);
|
||||||
std::byte * lhs_addr = reinterpret_cast<std::byte *>(lhs);
|
const std::byte * lhs_addr = reinterpret_cast<const std::byte *>(lhs);
|
||||||
|
|
||||||
if ((lhs_addr < parent_addr) || (parent_addr + parent_z < lhs_addr)) {
|
if ((lhs_addr < parent_addr) || (parent_addr + parent_z < lhs_addr)) {
|
||||||
if (may_throw_flag) {
|
if (may_throw_flag) {
|
||||||
|
|
@ -617,12 +641,21 @@ namespace xo {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
IObject * rhs = *lhs;
|
const void * rhs = *lhs;
|
||||||
|
|
||||||
|
if (!rhs)
|
||||||
|
return true;
|
||||||
|
|
||||||
auto parent_gen = tospace_generation_of(parent);
|
auto parent_gen = tospace_generation_of(parent);
|
||||||
auto rhs_gen = tospace_generation_of(rhs);
|
auto rhs_gen = tospace_generation_of(rhs);
|
||||||
|
|
||||||
|
log && log(xtag("C", rhs),
|
||||||
|
xtag("gen(P)", parent_gen), xtag("gen(C)", rhs_gen),
|
||||||
|
xtag("P.before-ckp", is_before_checkpoint(parent)),
|
||||||
|
xtag("C.before-ckp", is_before_checkpoint(rhs)));
|
||||||
|
|
||||||
switch(parent_gen) {
|
switch(parent_gen) {
|
||||||
case generation_result::nursery:
|
case generation_result::nursery:
|
||||||
if (is_before_checkpoint(parent)) {
|
if (is_before_checkpoint(parent)) {
|
||||||
|
|
@ -630,21 +663,25 @@ namespace xo {
|
||||||
case generation_result::nursery:
|
case generation_result::nursery:
|
||||||
if (is_before_checkpoint(rhs)) {
|
if (is_before_checkpoint(rhs)) {
|
||||||
/* no mlog entry needed */
|
/* no mlog entry needed */
|
||||||
|
log && log(xtag("msg", "N1->N1 - trivial"));
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
/* need to check mlog */
|
/* need to check mlog */
|
||||||
;
|
log && log(xtag("msg", "N1->N0 - xgen"));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case generation_result::tenured:
|
case generation_result::tenured:
|
||||||
/* no mlog entry needed */
|
/* no mlog entry needed */
|
||||||
|
log && log(xtag("msg", "N1->T - trivial"));
|
||||||
return true;
|
return true;
|
||||||
case generation_result::not_found:
|
case generation_result::not_found:
|
||||||
/* possible non-gc rhs */
|
/* possible non-gc rhs */
|
||||||
|
log && log(xtag("msg", "non-gc rhs - trivial"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* no mlog entry needed */
|
/* no mlog entry needed */
|
||||||
|
log && log(xtag("msg", "N0->any - trivial"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -652,16 +689,21 @@ namespace xo {
|
||||||
switch(rhs_gen) {
|
switch(rhs_gen) {
|
||||||
case generation_result::nursery:
|
case generation_result::nursery:
|
||||||
/* need to check mlog */
|
/* need to check mlog */
|
||||||
|
log && log(xtag("msg", "T->N - xgen"));
|
||||||
break;
|
break;
|
||||||
case generation_result::tenured:
|
case generation_result::tenured:
|
||||||
/* no mlog entry needed */
|
/* no mlog entry needed */
|
||||||
|
log && log(xtag("msg", "T->T - trivial"));
|
||||||
return true;
|
return true;
|
||||||
case generation_result::not_found:
|
case generation_result::not_found:
|
||||||
/* possible non-gc rhs */
|
/* possible non-gc rhs */
|
||||||
|
log && log(xtag("msg", "non-gc rhs - trivial"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
case generation_result::not_found:
|
case generation_result::not_found:
|
||||||
/* already excluded -> impossible */
|
/* already excluded -> impossible */
|
||||||
|
log && log(xtag("msg", "assert"));
|
||||||
assert(false && "already verified parent owned by GC");
|
assert(false && "already verified parent owned by GC");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -669,7 +711,7 @@ namespace xo {
|
||||||
* search mutation log + verify such entry exists
|
* search mutation log + verify such entry exists
|
||||||
*/
|
*/
|
||||||
for (MutationLogEntry & mlog : *(mutation_log_[role2int(role::to_space)])) {
|
for (MutationLogEntry & mlog : *(mutation_log_[role2int(role::to_space)])) {
|
||||||
if ((mlog.parent() == parent) && (mlog.lhs() == lhs)) {
|
if ((mlog.parent() == parent) && ((const void * const *)mlog.lhs() == lhs)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
mlog.lhs();
|
mlog.lhs();
|
||||||
|
|
@ -872,7 +914,7 @@ namespace xo {
|
||||||
for (MutationLogEntry & from_entry : *from_mlog)
|
for (MutationLogEntry & from_entry : *from_mlog)
|
||||||
{
|
{
|
||||||
if (log) {
|
if (log) {
|
||||||
if (i_from % 10000 == 0)
|
if (i_from % 10000 == 0 || true)
|
||||||
log(xtag("i_from", i_from));
|
log(xtag("i_from", i_from));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -905,8 +947,12 @@ namespace xo {
|
||||||
|
|
||||||
++n_rescue;
|
++n_rescue;
|
||||||
|
|
||||||
|
log && log(xtag("parent", parent), xtag("act", "move child"), xtag("child.from", child_from));
|
||||||
|
|
||||||
Object::_deep_move(child_from, this, per_type_stats);
|
Object::_deep_move(child_from, this, per_type_stats);
|
||||||
|
|
||||||
|
log && log(xtag("child.to", child_from->_destination()));
|
||||||
|
|
||||||
// C forwards to C', fall thru to parent fixup below
|
// C forwards to C', fall thru to parent fixup below
|
||||||
// (a) T->N1'
|
// (a) T->N1'
|
||||||
// (c) T->T
|
// (c) T->T
|
||||||
|
|
@ -924,13 +970,24 @@ namespace xo {
|
||||||
|
|
||||||
IObject * child_to = child_from->_destination();
|
IObject * child_to = child_from->_destination();
|
||||||
|
|
||||||
|
log && log(xtag("act", "fixup parent"), xtag("parent", parent), xtag("lhs", from_entry.lhs()), xtag("child.from", child_from), xtag("child.to", child_to));
|
||||||
|
|
||||||
from_entry.fixup_parent_child_moved(child_to);
|
from_entry.fixup_parent_child_moved(child_to);
|
||||||
|
|
||||||
|
{
|
||||||
|
// verify fixup was effective
|
||||||
|
IObject * child_from2 = from_entry.child();
|
||||||
|
assert(child_from2 == child_to);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// P->C', loc(C') in {N1', T'}
|
// P->C', loc(C') in {N1', T'}
|
||||||
|
|
||||||
if (tospace_generation_of(child_to) == generation_result::nursery) {
|
if (tospace_generation_of(child_to) == generation_result::nursery) {
|
||||||
// (b) loc(P)=T, loc(C')=N1'; also case (a)
|
// (b) loc(P)=T, loc(C')=N1'; also case (a)
|
||||||
|
|
||||||
|
log && log(xtag("act", "still xgen -> keep mlog entry"));
|
||||||
|
|
||||||
// still have xgen pointer, so need mlog for it
|
// still have xgen pointer, so need mlog for it
|
||||||
to_mlog->push_back(from_entry);
|
to_mlog->push_back(from_entry);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -972,6 +1029,8 @@ namespace xo {
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
log && log("defer");
|
||||||
|
|
||||||
// loc(P) = N1, loc(C) = N0, P may be garbage
|
// loc(P) = N1, loc(C) = N0, P may be garbage
|
||||||
// Includes cases:
|
// Includes cases:
|
||||||
// (e) P->C, C not moved
|
// (e) P->C, C not moved
|
||||||
|
|
@ -1347,7 +1406,7 @@ namespace xo {
|
||||||
void
|
void
|
||||||
GC::execute_gc(generation upto)
|
GC::execute_gc(generation upto)
|
||||||
{
|
{
|
||||||
scope log(XO_DEBUG(config_.stats_flag_));
|
scope log(XO_DEBUG(config_.stats_flag_ || config_.debug_flag_));
|
||||||
|
|
||||||
auto t0 = std::chrono::steady_clock::now();
|
auto t0 = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,18 +42,24 @@ namespace xo {
|
||||||
Object::_forward(IObject * src,
|
Object::_forward(IObject * src,
|
||||||
gc::IAlloc * gc)
|
gc::IAlloc * gc)
|
||||||
{
|
{
|
||||||
|
scope log(XO_DEBUG(gc->debug_flag()), xtag("src", src));
|
||||||
|
|
||||||
if (!src)
|
if (!src)
|
||||||
return src;
|
return src;
|
||||||
|
|
||||||
if (src->_is_forwarded())
|
if (src->_is_forwarded()) {
|
||||||
|
log && log("already forwarded", xtag("dest", src->_offset_destination(src)));
|
||||||
return src->_offset_destination(src);
|
return src->_offset_destination(src);
|
||||||
|
}
|
||||||
|
|
||||||
if (gc->check_move(src)) {
|
if (gc->check_move(src)) {
|
||||||
|
log && log("needs forwarding");
|
||||||
Object::_shallow_move(src, gc);
|
Object::_shallow_move(src, gc);
|
||||||
|
|
||||||
/* *src is now a forwarding pointer to a copy in to-space */
|
/* *src is now a forwarding pointer to a copy in to-space */
|
||||||
return src->_offset_destination(src);
|
return src->_offset_destination(src);
|
||||||
} else {
|
} else {
|
||||||
|
log && log("already tenured + incr collection");
|
||||||
/* don't move tenured objects during incremental collection */
|
/* don't move tenured objects during incremental collection */
|
||||||
return src;
|
return src;
|
||||||
}
|
}
|
||||||
|
|
@ -62,6 +68,8 @@ namespace xo {
|
||||||
IObject *
|
IObject *
|
||||||
Object::_deep_move(IObject * from_src, gc::GC * gc, gc::ObjectStatistics * /*stats*/)
|
Object::_deep_move(IObject * from_src, gc::GC * gc, gc::ObjectStatistics * /*stats*/)
|
||||||
{
|
{
|
||||||
|
scope log(XO_DEBUG(gc->config().debug_flag_));
|
||||||
|
|
||||||
using gc::generation;
|
using gc::generation;
|
||||||
|
|
||||||
if (!from_src)
|
if (!from_src)
|
||||||
|
|
@ -146,13 +154,15 @@ namespace xo {
|
||||||
do {
|
do {
|
||||||
fixup_work = 0;
|
fixup_work = 0;
|
||||||
|
|
||||||
auto fixup_generation = [gc, &gray_lo_v](generation gen) {
|
auto fixup_generation = [gc, &log, &gray_lo_v](generation gen) {
|
||||||
std::size_t work = 0;
|
std::size_t work = 0;
|
||||||
while(gray_lo_v[gen2int(gen)] < gc->free_ptr(gen)) {
|
while(gray_lo_v[gen2int(gen)] < gc->free_ptr(gen)) {
|
||||||
Object * x = reinterpret_cast<Object *>(gray_lo_v[gen2int(gen)]);
|
Object * x = reinterpret_cast<Object *>(gray_lo_v[gen2int(gen)]);
|
||||||
|
|
||||||
// update per-class stats here
|
// update per-class stats here
|
||||||
|
|
||||||
|
log && log("fwd children", xtag("x", x));
|
||||||
|
|
||||||
std::size_t xz = x->_forward_children(gc);
|
std::size_t xz = x->_forward_children(gc);
|
||||||
|
|
||||||
// must pad xz to multiple of word size,
|
// must pad xz to multiple of word size,
|
||||||
|
|
|
||||||
|
|
@ -202,6 +202,29 @@ namespace xo {
|
||||||
vector_type member2_;
|
vector_type member2_;
|
||||||
bool ctor_ran_ = false;
|
bool ctor_ran_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef NOT_YET
|
||||||
|
struct MemberType2 {
|
||||||
|
public:
|
||||||
|
MemberType2() = default;
|
||||||
|
/** GC hooks rely on copy constructor. But can't write it without allocator state.
|
||||||
|
* Therefore: need copy-like constructor that takes allocator argument
|
||||||
|
**/
|
||||||
|
|
||||||
|
template <typename Allocator>
|
||||||
|
explicit MemberType2(Allocator & alloc, uint64 payload) {
|
||||||
|
using traits = gc_allocator_traits<Allocator>;
|
||||||
|
|
||||||
|
uint64_t * ptr = traits::allocate(alloc, 1);
|
||||||
|
|
||||||
|
this->payload_ = payload;
|
||||||
|
this->ctor_ran_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t * payload_ = nullptr;
|
||||||
|
bool ctor_ran_ = false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("vector_custom_allocator", "[alloc][vector]")
|
TEST_CASE("vector_custom_allocator", "[alloc][vector]")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue