xo-gc: bugfixes for GCObjectStore, unit test exapnded
In particular: drop casual assignment to DList.rest_, will break acyclic assumption of DList.size()
This commit is contained in:
parent
53d9ab7747
commit
c4eb58f6ff
5 changed files with 368 additions and 194 deletions
|
|
@ -16,6 +16,11 @@ namespace xo {
|
|||
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:
|
||||
|
|
@ -28,16 +33,21 @@ namespace xo {
|
|||
using typeseq = xo::reflect::typeseq;
|
||||
|
||||
public:
|
||||
explicit GCObjectStore(const GCObjectStoreConfig & cfg);
|
||||
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 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}); }
|
||||
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
|
||||
|
|
@ -124,8 +134,7 @@ namespace xo {
|
|||
* to call AGCObject visitor method (forward_children()) on each
|
||||
* object stored here.
|
||||
**/
|
||||
void verify_ok(obj<AGCObjectVisitor> gc,
|
||||
X1VerifyStats * p_verify_stats) noexcept;
|
||||
void verify_ok(obj<AGCObjectVisitor> gc) noexcept;
|
||||
|
||||
/** Register object type with this collector.
|
||||
* Provides shallow copy and pointer forwarding for instances of this
|
||||
|
|
@ -142,11 +151,14 @@ namespace xo {
|
|||
/** 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(obj<AGCObjectVisitor> gc,
|
||||
obj<AGCObject> from_src,
|
||||
const AGCObject * root_iface,
|
||||
void ** root_data,
|
||||
Generation upto);
|
||||
|
||||
/** move interior subgraph at @p from_src to to-space.
|
||||
|
|
@ -158,6 +170,16 @@ namespace xo {
|
|||
void * from_src,
|
||||
Generation upto);
|
||||
|
||||
#ifdef NOT_YET
|
||||
/** Target for GCObjectVisitor facet
|
||||
* During gc phase (@p reason is 'forward')
|
||||
* 1. evacuate object at @p *lhs_data to to-space.
|
||||
* 2. replace @p *lhs_data with forwarding pointer
|
||||
* to new location.
|
||||
**/
|
||||
void visit_child(VisitReason reason, AGCObject * lhs_iface, void ** lhs_data);
|
||||
#endif
|
||||
|
||||
/** 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
|
||||
|
|
@ -172,12 +194,11 @@ namespace xo {
|
|||
|
||||
/** categorize fop {@p lhs_iface, @p lhs_data}
|
||||
* based on location of @p lhs_data.
|
||||
* Update @p *p_verify_stats based on the result:
|
||||
* 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,
|
||||
X1VerifyStats * p_verify_stats);
|
||||
void * lhs_data);
|
||||
|
||||
/** Cleanup at the end of a gc cycle.
|
||||
* Reset from-space
|
||||
|
|
@ -248,6 +269,8 @@ namespace xo {
|
|||
**/
|
||||
std::array<DArena*, c_max_generation> space_[c_n_role];
|
||||
|
||||
/** dedicated counters. updated by .verify_aux() **/
|
||||
X1VerifyStats * p_verify_stats_ = nullptr;
|
||||
};
|
||||
|
||||
} /*namespace mm*/
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ namespace xo {
|
|||
|
||||
DX1Collector::DX1Collector(const X1CollectorConfig & cfg)
|
||||
: config_{cfg},
|
||||
gco_store_{cfg.gco_store_config()},
|
||||
gco_store_{cfg.gco_store_config(), &verify_stats_},
|
||||
mlog_store_{cfg.mlog_config(), &gco_store_}
|
||||
{
|
||||
assert(config_.arena_config_.header_.size_bits_ +
|
||||
|
|
@ -417,7 +417,7 @@ namespace xo {
|
|||
}
|
||||
|
||||
// 3. scan to-space for each generation
|
||||
gco_store_.verify_ok(this->ref<AGCObjectVisitor>(), &(this->verify_stats_));
|
||||
gco_store_.verify_ok(this->ref<AGCObjectVisitor>());
|
||||
|
||||
// 4. scan mutation logs
|
||||
mlog_store_.verify_ok(&gco_store_,
|
||||
|
|
@ -576,7 +576,9 @@ namespace xo {
|
|||
xtag("slot.root()", slot.root()),
|
||||
xtag("slot.root()->data_", slot.root()->data_));
|
||||
|
||||
void * root_to = gco_store_.deep_move_root(this->ref<AGCObjectVisitor>(), *slot.root(), upto);
|
||||
void * root_to = gco_store_.deep_move_root(this->ref<AGCObjectVisitor>(),
|
||||
slot.root()->iface(),
|
||||
(void **)&(slot.root()->data_), upto);
|
||||
|
||||
slot.root()->reset_opaque(root_to);
|
||||
|
||||
|
|
@ -604,7 +606,7 @@ namespace xo {
|
|||
}
|
||||
case VisitReason::code::verify:
|
||||
// called during verify_ok
|
||||
gco_store_.verify_aux(lhs_iface, *lhs_data, &verify_stats_);
|
||||
gco_store_.verify_aux(lhs_iface, *lhs_data);
|
||||
break;
|
||||
default:
|
||||
// should be unreachable
|
||||
|
|
@ -612,37 +614,6 @@ namespace xo {
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef OBSOLETE
|
||||
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_);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
auto
|
||||
DX1Collector::alloc(typeseq t, size_type z) noexcept -> value_type
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <xo/stringtable2/String.hpp>
|
||||
|
||||
#include <xo/arena/DArenaIterator.hpp>
|
||||
#include <xo/arena/backtrace.hpp>
|
||||
|
||||
#include <xo/facet/TypeRegistry.hpp>
|
||||
#include <xo/indentlog/scope.hpp>
|
||||
|
|
@ -31,8 +32,9 @@ namespace xo {
|
|||
|
||||
namespace mm {
|
||||
|
||||
GCObjectStore::GCObjectStore(const GCObjectStoreConfig & cfg)
|
||||
: config_{cfg}
|
||||
GCObjectStore::GCObjectStore(const GCObjectStoreConfig & cfg,
|
||||
X1VerifyStats * p_verify_stats)
|
||||
: config_{cfg}, p_verify_stats_{p_verify_stats}
|
||||
{
|
||||
assert(config_.arena_config_.header_.size_bits_ +
|
||||
config_.arena_config_.header_.age_bits_ +
|
||||
|
|
@ -473,6 +475,36 @@ namespace xo {
|
|||
return (g < upto);
|
||||
}
|
||||
|
||||
#ifdef NOT_YET
|
||||
void
|
||||
GCObjectStore::visit_child(VisitReason reason,
|
||||
AGCObject * lhs_iface,
|
||||
void ** lhs_data)
|
||||
{
|
||||
// MAYBE: adapter distinct from DX1Collector that supports GCObjectVisitor facet,
|
||||
// calls DX1Collector::_verify_aux()
|
||||
|
||||
switch (reason.code()) {
|
||||
case VisitReason::code::forward:
|
||||
{
|
||||
Generation upto = runstate_.gc_upto();
|
||||
|
||||
// called during collection phase
|
||||
this->forward_inplace_aux
|
||||
(this->ref<AGCObjectVisitor>(), lhs_iface, lhs_data, upto);
|
||||
break;
|
||||
}
|
||||
case VisitReason::code::verify:
|
||||
// called during verify_ok
|
||||
gco_store_.verify_aux(lhs_iface, *lhs_data);
|
||||
break;
|
||||
default:
|
||||
// should be unreachable
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
GCObjectStore::forward_inplace_aux(obj<AGCObjectVisitor> gc,
|
||||
AGCObject * lhs_iface,
|
||||
|
|
@ -558,7 +590,9 @@ namespace xo {
|
|||
*
|
||||
* This is guaranteed anyway, by alignment rules
|
||||
*/
|
||||
assert(alloc_z >= sizeof(uintptr_t));
|
||||
if (alloc_z < sizeof(uintptr_t)) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (this->is_forwarding_header(alloc_hdr)) {
|
||||
/* *lhs_data already refers to a forwarding pointer */
|
||||
|
|
@ -661,10 +695,9 @@ namespace xo {
|
|||
|
||||
void
|
||||
GCObjectStore::verify_aux(AGCObject * iface,
|
||||
void * data,
|
||||
X1VerifyStats * p_verify_stats)
|
||||
void * data)
|
||||
{
|
||||
//scope log(XO_DEBUG(config_.debug_flag_), xtag("data", data));
|
||||
scope log(XO_DEBUG(config_.debug_flag_));
|
||||
|
||||
(void)iface;
|
||||
|
||||
|
|
@ -678,14 +711,16 @@ namespace xo {
|
|||
if (!g2.is_sentinel()) {
|
||||
// verify failure - live pointer still refers to from-space
|
||||
|
||||
++(p_verify_stats->n_from_);
|
||||
print_backtrace_dwarf(true /*demangle*/);
|
||||
|
||||
++(p_verify_stats_->n_from_);
|
||||
} else {
|
||||
++(p_verify_stats->n_ext_);
|
||||
++(p_verify_stats_->n_ext_);
|
||||
}
|
||||
} else {
|
||||
assert(this->contains(Role::to_space(), data));
|
||||
|
||||
++(p_verify_stats->n_to_);
|
||||
++(p_verify_stats_->n_to_);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -733,8 +768,7 @@ namespace xo {
|
|||
}
|
||||
|
||||
void
|
||||
GCObjectStore::verify_ok(obj<AGCObjectVisitor> gc,
|
||||
X1VerifyStats * p_verify_stats) noexcept
|
||||
GCObjectStore::verify_ok(obj<AGCObjectVisitor> gc) noexcept
|
||||
{
|
||||
for (Generation g(0); g < config_.n_generation_; ++g) {
|
||||
const DArena * space = this->get_space(Role::to_space(), g);
|
||||
|
|
@ -742,7 +776,7 @@ namespace xo {
|
|||
for (const AllocInfo & info : *space) {
|
||||
|
||||
if (info.is_forwarding_tseq()) {
|
||||
++(p_verify_stats->n_fwd_);
|
||||
++(p_verify_stats_->n_fwd_);
|
||||
|
||||
} else {
|
||||
typeseq tseq(info.tseq());
|
||||
|
|
@ -755,15 +789,9 @@ namespace xo {
|
|||
// assembled fop for gc-aware object
|
||||
obj<AGCObject> gco(iface, const_cast<void *>(data));
|
||||
|
||||
// forward_children is hijacked here to verify
|
||||
// child pointer validity.
|
||||
//
|
||||
// Nested control reenters
|
||||
// X1Collector::forward_inplace() -> _verify_aux()
|
||||
//
|
||||
gco.visit_gco_children(VisitReason::forward(), gc);
|
||||
gco.visit_gco_children(VisitReason::verify(), gc);
|
||||
} else {
|
||||
++(p_verify_stats->n_no_iface_);
|
||||
++(p_verify_stats_->n_no_iface_);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -803,7 +831,8 @@ namespace xo {
|
|||
|
||||
void *
|
||||
GCObjectStore::deep_move_root(obj<AGCObjectVisitor> gc,
|
||||
obj<AGCObject> from_src,
|
||||
const AGCObject * root_iface,
|
||||
void ** root_data,
|
||||
Generation upto)
|
||||
{
|
||||
// NOTE:
|
||||
|
|
@ -815,22 +844,23 @@ namespace xo {
|
|||
|
||||
scope log(XO_DEBUG(config_.debug_flag_));
|
||||
|
||||
if (!from_src)
|
||||
if (!root_data || !*root_data)
|
||||
return nullptr;
|
||||
|
||||
bool src_in_from_space = this->contains(Role::from_space(),
|
||||
from_src.data());
|
||||
bool src_in_from_space = this->contains(Role::from_space(), *root_data);
|
||||
|
||||
if (src_in_from_space) {
|
||||
return this->_deep_move_gc_owned(gc, from_src.data(), upto);
|
||||
*root_data = this->_deep_move_gc_owned(gc, *root_data, upto);
|
||||
} else {
|
||||
// we aren't moving from_src, it's not gc-owned.
|
||||
// However weare moving all its gc-owned children
|
||||
// However we are moving all its gc-owned children
|
||||
|
||||
GCMoveCheckpoint gray_lo_v
|
||||
= this->snap_move_checkpoint(upto);
|
||||
|
||||
from_src.visit_gco_children(VisitReason::forward(), gc);
|
||||
auto root = obj<AGCObject>(root_iface, *root_data);
|
||||
|
||||
root.visit_gco_children(VisitReason::forward(), gc);
|
||||
|
||||
// For each generation g:
|
||||
// traverse objects newer than gray_lo_v[g], to make sure children
|
||||
|
|
@ -840,8 +870,11 @@ namespace xo {
|
|||
//
|
||||
this->_forward_children_until_fixpoint(gc, upto, gray_lo_v);
|
||||
|
||||
return from_src.data();
|
||||
// reminder: *root_data preserved
|
||||
|
||||
}
|
||||
|
||||
return *root_data;
|
||||
}
|
||||
|
||||
void *
|
||||
|
|
|
|||
|
|
@ -23,10 +23,17 @@ namespace xo {
|
|||
void
|
||||
DMockCollector::visit_child(VisitReason reason, AGCObject * lhs_iface, void ** lhs_data)
|
||||
{
|
||||
(void)reason;
|
||||
|
||||
switch (reason.code()) {
|
||||
case VisitReason::code::forward:
|
||||
p_gco_store_->forward_inplace_aux
|
||||
(this->ref<AGCObjectVisitor>(), lhs_iface, lhs_data, upto_);
|
||||
break;
|
||||
case VisitReason::code::verify:
|
||||
p_gco_store_->verify_aux(lhs_iface, *lhs_data);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
std::byte *
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
**/
|
||||
|
||||
#include <xo/gc/GCObjectStore.hpp>
|
||||
#include <xo/gc/X1VerifyStats.hpp>
|
||||
#include "MockCollector.hpp"
|
||||
|
||||
#include <xo/object2/ListOps.hpp>
|
||||
|
|
@ -13,6 +14,7 @@
|
|||
#include <xo/alloc2/GCObjectVisitor.hpp>
|
||||
#include <xo/alloc2/GCObject.hpp>
|
||||
#include <xo/alloc2/Arena.hpp>
|
||||
#include <xo/facet/TypeRegistry.hpp>
|
||||
#include <xo/arena/print.hpp>
|
||||
#include <xo/indentlog/scope.hpp>
|
||||
#include <xo/indentlog/print/tag.hpp>
|
||||
|
|
@ -28,6 +30,7 @@ namespace ut {
|
|||
using xo::mm::DMockCollector;
|
||||
using xo::mm::GCObjectStoreConfig;
|
||||
using xo::mm::GCObjectStore;
|
||||
using xo::mm::X1VerifyStats;
|
||||
using xo::mm::AGCObject;
|
||||
using xo::mm::AGCObjectVisitor;
|
||||
using xo::mm::Generation;
|
||||
|
|
@ -39,6 +42,7 @@ namespace ut {
|
|||
using xo::mm::AllocInfo;
|
||||
using xo::mm::c_max_generation;
|
||||
using xo::facet::obj;
|
||||
using xo::facet::TypeRegistry;
|
||||
using xo::facet::typeseq;
|
||||
using xo::facet::impl_for;
|
||||
using xo::rng::xoshiro256ss;
|
||||
|
|
@ -50,14 +54,23 @@ namespace ut {
|
|||
using std::uint32_t;
|
||||
|
||||
namespace {
|
||||
enum class TestGraphType {
|
||||
/* list cell pointing to itself */
|
||||
selfcycle,
|
||||
/* random object graph */
|
||||
random,
|
||||
};
|
||||
|
||||
struct Testcase {
|
||||
explicit Testcase(uint32_t n_gen, uint32_t n_survive,
|
||||
size_t gc_z, uint32_t type_z,
|
||||
bool do_type_registration,
|
||||
size_t report_z,
|
||||
size_t error_z,
|
||||
TestGraphType obj_graph_type,
|
||||
uint32_t n_test_obj,
|
||||
uint32_t n_test_assign)
|
||||
uint32_t n_test_assign,
|
||||
bool debug_flag)
|
||||
: n_gen_{n_gen},
|
||||
n_survive_{n_survive},
|
||||
gc_size_{gc_z},
|
||||
|
|
@ -65,8 +78,10 @@ namespace ut {
|
|||
do_type_registration_{do_type_registration},
|
||||
report_size_{report_z},
|
||||
error_size_{error_z},
|
||||
obj_graph_type_{obj_graph_type},
|
||||
n_test_obj_{n_test_obj},
|
||||
n_test_assign_{n_test_assign}
|
||||
n_test_assign_{n_test_assign},
|
||||
debug_flag_{debug_flag}
|
||||
{}
|
||||
|
||||
/** number of generations in gco store **/
|
||||
|
|
@ -89,12 +104,19 @@ namespace ut {
|
|||
size_t report_size_ = 0;
|
||||
/** size for error-output arena **/
|
||||
size_t error_size_ = 0;
|
||||
/** object graph type **/
|
||||
TestGraphType obj_graph_type_ = TestGraphType::random;
|
||||
/** #of cells in random object graph **/
|
||||
uint32_t n_test_obj_ = 0;
|
||||
/** #of random assignments to attempt (these may create cycles, for example) **/
|
||||
uint32_t n_test_assign_ = 0;
|
||||
|
||||
/** true to enable debug when attempting this test case **/
|
||||
bool debug_flag_ = false;
|
||||
};
|
||||
|
||||
constexpr TestGraphType c_selfcycle = TestGraphType::selfcycle;
|
||||
constexpr TestGraphType c_random = TestGraphType::random;
|
||||
constexpr uint32_t c_report_z1 = 64 * 1024;
|
||||
constexpr uint32_t c_error_z1 = 16 * 1024;
|
||||
|
||||
|
|
@ -102,12 +124,14 @@ namespace ut {
|
|||
// note: report_z: 64k not sufficient for report_object_ages()
|
||||
|
||||
/** n_gen, n_survive, gc_size, object_type_z, do_type_registration, report_z, error_z, n_obj, n_test_assign **/
|
||||
Testcase(2, 4, 16 * 1024, 8 * 128, false, c_report_z1, c_error_z1, 0, 0),
|
||||
Testcase(2, 4, 16 * 1024, 8 * 128, true, c_report_z1, c_error_z1, 1, 0),
|
||||
Testcase(2, 4, 16 * 1024, 8 * 128, true, c_report_z1, c_error_z1, 2, 0),
|
||||
Testcase(2, 4, 16 * 1024, 8 * 128, true, c_report_z1, c_error_z1, 4, 0),
|
||||
Testcase(2, 4, 16 * 1024, 8 * 128, true, c_report_z1, c_error_z1, 8, 4),
|
||||
Testcase(2, 4, 16 * 1024, 8 * 128, true, c_report_z1, c_error_z1, 16, 7),
|
||||
Testcase(2, 4, 16 * 1024, 8 * 128, false, c_report_z1, c_error_z1, c_random, 0, 0, false),
|
||||
Testcase(2, 4, 16 * 1024, 8 * 128, true, c_report_z1, c_error_z1, c_selfcycle, 1, 0, false),
|
||||
Testcase(2, 4, 16 * 1024, 8 * 128, true, c_report_z1, c_error_z1, c_random, 1, 0, false),
|
||||
Testcase(2, 4, 16 * 1024, 8 * 128, true, c_report_z1, c_error_z1, c_random, 2, 13, false),
|
||||
Testcase(2, 4, 16 * 1024, 8 * 128, true, c_report_z1, c_error_z1, c_random, 2, 25, false),
|
||||
Testcase(2, 4, 16 * 1024, 8 * 128, true, c_report_z1, c_error_z1, c_random, 5, 0, false),
|
||||
Testcase(2, 4, 16 * 1024, 8 * 128, true, c_report_z1, c_error_z1, c_random, 4, 2, false),
|
||||
Testcase(2, 4, 16 * 1024, 8 * 128, true, c_report_z1, c_error_z1, c_random, 50, 25, false),
|
||||
};
|
||||
|
||||
/** record capturing some stats for a (randomly created) gc-aware object **/
|
||||
|
|
@ -123,6 +147,35 @@ namespace ut {
|
|||
typeseq tseq_;
|
||||
};
|
||||
|
||||
/** Create two isomorphic object graphs.
|
||||
* Each graph comprises a single DList cell
|
||||
* that points to itself
|
||||
**/
|
||||
void
|
||||
selfcycle_object_graph(std::vector<Recd> * p_v1,
|
||||
GCObjectStore * p_gcos,
|
||||
std::vector<Recd> * p_v2,
|
||||
DArena * arena2)
|
||||
{
|
||||
auto alloc1 = obj<AAllocator,DArena>(p_gcos->new_space());
|
||||
auto alloc2 = obj<AAllocator,DArena>(arena2);
|
||||
|
||||
auto t1 = DBoolean::box(alloc1, true);
|
||||
auto t2 = DBoolean::box(alloc2, true);
|
||||
|
||||
auto l1 = ListOps::cons(alloc1, t1, ListOps::nil());
|
||||
auto l2 = ListOps::cons(alloc2, t2, ListOps::nil());
|
||||
|
||||
// shortcut. Can get away with skipping mm_do_assign(),
|
||||
// because we know lhs of assignment is in the youngest generation
|
||||
|
||||
l1->head_ = l1; // l1->assign_head(gc, l1); // need collector facet
|
||||
l2->head_ = l2; // l2->assign_head(gc, l2); // need collector facet
|
||||
|
||||
p_v1->push_back(Recd(l1, sizeof(DList), typeseq::id<DList>()));
|
||||
p_v2->push_back(Recd(l2, sizeof(DList), typeseq::id<DList>()));
|
||||
}
|
||||
|
||||
/** Create two isomorphic random object graphs containing @p n_obj nodes
|
||||
* Using a few basic data types from xo-object2
|
||||
* DBoolean
|
||||
|
|
@ -147,6 +200,8 @@ namespace ut {
|
|||
std::vector<Recd> * p_v2,
|
||||
DArena * p_arena2)
|
||||
{
|
||||
scope log(XO_DEBUG(true));
|
||||
|
||||
if (n_obj == 0)
|
||||
return;
|
||||
|
||||
|
|
@ -229,62 +284,136 @@ namespace ut {
|
|||
|
||||
for (uint32_t j = 0; j < n_assign; ++j) {
|
||||
// choose an object at random
|
||||
uint32_t sample = (*p_rgen)() % n_obj;
|
||||
uint32_t lhs_ix = (*p_rgen)() % n_obj;
|
||||
|
||||
assert(sample < p_v->size());
|
||||
assert(lhs_ix < p_v->size());
|
||||
|
||||
// is it a list cell?
|
||||
auto xj = obj<AGCObject,DList>::from((*p_v)[sample].gco_);
|
||||
auto xj2 = obj<AGCObject,DList>::from((*p_v2)[sample].gco_);
|
||||
auto xj1 = obj<AGCObject,DList>::from((*p_v)[lhs_ix].gco_);
|
||||
auto xj2 = obj<AGCObject,DList>::from((*p_v2)[lhs_ix].gco_);
|
||||
|
||||
if (xj) {
|
||||
if (xj1) {
|
||||
assert(xj2);
|
||||
|
||||
// flip a coin -- try modifying one of {car, cdr}
|
||||
sample = (*p_rgen)() % 100;
|
||||
uint32_t sample = (*p_rgen)() % 100;
|
||||
|
||||
if (sample < 50) {
|
||||
// modify head. skip usual gc write-barrier stuff
|
||||
|
||||
sample = (*p_rgen)() % n_obj;
|
||||
// rhs could even be xj itself
|
||||
xj->head_ = (*p_v)[sample].gco_;
|
||||
xj2->head_ = (*p_v2)[sample].gco_;
|
||||
} else {
|
||||
// modify rest, maybe.
|
||||
uint32_t rhs_ix = (*p_rgen)() % n_obj;
|
||||
|
||||
sample = (*p_rgen)() % n_obj;
|
||||
auto rhs = obj<AGCObject,DList>::from((*p_v)[sample].gco_);
|
||||
auto rhs2 = obj<AGCObject,DList>::from((*p_v2)[sample].gco_);
|
||||
auto rhs1 = (*p_v)[rhs_ix].gco_;
|
||||
auto rhs2 = (*p_v2)[rhs_ix].gco_;
|
||||
|
||||
if (rhs) {
|
||||
// modify rest. skip usual gc write-barrier stuff
|
||||
|
||||
assert(rhs2);
|
||||
|
||||
xj->rest_ = rhs.data();
|
||||
xj2->rest_ = rhs2.data();
|
||||
if (log) {
|
||||
log("replacing edge in random object graph");
|
||||
log(xtag("n-obj", n_obj));
|
||||
log(xtag("lhs-ix", lhs_ix));
|
||||
log(xtag("rhs-ix", rhs_ix));
|
||||
log(xtag("rhs.tname", TypeRegistry::id2name(rhs1._typeseq())));
|
||||
}
|
||||
|
||||
// rhs1 could even be xj1 itself (in which case rhs2 is xj2)
|
||||
xj1->head_ = rhs1;
|
||||
xj2->head_ = rhs2;
|
||||
} else {
|
||||
// don't modify DList.rest_, risks losing acyclic propertly.
|
||||
// GCObjectStore handles this, but DList.size() assumes
|
||||
// list is acyclic
|
||||
}
|
||||
}
|
||||
}
|
||||
} /*random_object_graph*/
|
||||
} /*namespace*/
|
||||
|
||||
namespace {
|
||||
// aux functions specific to GCObjectStore-1 unit test below
|
||||
|
||||
void
|
||||
gcos_install_test_types(const Testcase & tc,
|
||||
GCObjectStore * p_gcos)
|
||||
{
|
||||
// verify that GCOS recongnizes as registered,
|
||||
// the types we intend using for unit test
|
||||
|
||||
if (tc.do_type_registration_) {
|
||||
{
|
||||
REQUIRE(p_gcos->install_type(impl_for<AGCObject,DBoolean>()));
|
||||
REQUIRE(p_gcos->is_type_installed(typeseq::id<DBoolean>()));
|
||||
}
|
||||
{
|
||||
REQUIRE(p_gcos->install_type(impl_for<AGCObject,DList>()));
|
||||
REQUIRE(p_gcos->is_type_installed(typeseq::id<DList>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
gcos_verify_arena_partitioning(const Testcase & tc,
|
||||
const GCObjectStore & gcos)
|
||||
{
|
||||
Generation g0{0};
|
||||
Generation g1{1};
|
||||
Generation gn{tc.n_gen_};
|
||||
|
||||
// verify basic arena partitioning + sizing
|
||||
|
||||
REQUIRE(g0 != g1);
|
||||
REQUIRE(gcos.new_space());
|
||||
REQUIRE(gcos.new_space() == gcos.get_space(Role::to_space(), g0));
|
||||
REQUIRE(gcos.new_space()->reserved() >= tc.gc_size_);
|
||||
REQUIRE(gcos.from_space(g0));
|
||||
|
||||
for (Generation gi = g1; gi < tc.n_gen_; ++gi) {
|
||||
// all configured generations exist
|
||||
REQUIRE(gcos.to_space(gi));
|
||||
REQUIRE(gcos.from_space(gi));
|
||||
|
||||
// to- and from- space are distinct
|
||||
REQUIRE(gcos.to_space(gi) != gcos.from_space(gi));
|
||||
|
||||
// arenas for different generations are distinct
|
||||
for (Generation gj = g0; gj < gi; ++gj) {
|
||||
REQUIRE(gcos.to_space(gi) != gcos.to_space(gj));
|
||||
REQUIRE(gcos.from_space(gi) != gcos.to_space(gj));
|
||||
|
||||
REQUIRE(gcos.to_space(gi) != gcos.from_space(gj));
|
||||
REQUIRE(gcos.from_space(gi) != gcos.to_space(gj));
|
||||
}
|
||||
}
|
||||
|
||||
// generations that weren't requested, don't exist
|
||||
if (gn < c_max_generation) {
|
||||
REQUIRE(!gcos.to_space(gn));
|
||||
REQUIRE(!gcos.from_space(gn));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("GCObjectStore-1", "[GCObjectStore]")
|
||||
{
|
||||
constexpr bool c_debug_flag = true;
|
||||
scope log(XO_DEBUG(c_debug_flag), "GCObjectStore test");
|
||||
scope log0(XO_DEBUG(c_debug_flag), "GCObjectStore test");
|
||||
|
||||
std::uint64_t seed = 12168164826603821466ul;
|
||||
//random_seed(&seed);
|
||||
log && log(xtag("seed", seed));
|
||||
|
||||
auto rgen = xoshiro256ss(seed);
|
||||
log0 && log0(xtag("seed", seed));
|
||||
|
||||
for (size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) {
|
||||
// Loop iterations here are independent.
|
||||
// Could execute test cases in any order
|
||||
|
||||
// deterministic seed choice for each testcase
|
||||
// -> individual cases preserve rng behavior
|
||||
// regardless of testcase order and/or subsetting
|
||||
|
||||
auto rgen = xoshiro256ss(seed + i_tc);
|
||||
|
||||
const Testcase & tc = s_testcase_v[i_tc];
|
||||
|
||||
scope log1(XO_DEBUG(tc.debug_flag_), "testcase loop", xtag("i_tc", i_tc));
|
||||
|
||||
INFO(tostr(xtag("i_tc", i_tc), xtag("n_tc", n_tc)));
|
||||
|
||||
/** config for each half-space **/
|
||||
|
|
@ -298,7 +427,7 @@ namespace ut {
|
|||
tc.n_gen_,
|
||||
tc.n_survive_,
|
||||
tc.object_type_z_,
|
||||
c_debug_flag);
|
||||
tc.debug_flag_);
|
||||
|
||||
/** Parallel arena for reference
|
||||
*
|
||||
|
|
@ -330,64 +459,24 @@ namespace ut {
|
|||
.with_store_header_flag(true));
|
||||
obj<AAllocator,DArena> error_mm(&error_arena);
|
||||
|
||||
X1VerifyStats verify_stats;
|
||||
|
||||
// object type storage will be empty unless we install a type.
|
||||
GCObjectStore gcos(gcos_config);
|
||||
|
||||
// scaffold mock collector doing incremental collection
|
||||
DMockCollector mock_gc(&gcos, Generation{0});
|
||||
auto mock_gc_visitor = mock_gc.ref<AGCObjectVisitor>();
|
||||
|
||||
REQUIRE(gcos.is_type_installed(typeseq::id<DList>()) == false);
|
||||
REQUIRE(gcos.is_type_installed(typeseq::id<DBoolean>()) == false);
|
||||
GCObjectStore gcos(gcos_config, &verify_stats);
|
||||
|
||||
Generation g0{0};
|
||||
Generation g1{1};
|
||||
Generation gn{tc.n_gen_};
|
||||
|
||||
// install gc-aware types that we intend using in unit test
|
||||
if (tc.do_type_registration_) {
|
||||
{
|
||||
REQUIRE(gcos.install_type(impl_for<AGCObject,DBoolean>()));
|
||||
REQUIRE(gcos.is_type_installed(typeseq::id<DBoolean>()));
|
||||
}
|
||||
{
|
||||
REQUIRE(gcos.install_type(impl_for<AGCObject,DList>()));
|
||||
REQUIRE(gcos.is_type_installed(typeseq::id<DList>()));
|
||||
}
|
||||
}
|
||||
// scaffold mock collector doing incremental collection
|
||||
DMockCollector mock_gc(&gcos, g1);
|
||||
auto mock_gc_visitor = mock_gc.ref<AGCObjectVisitor>();
|
||||
|
||||
// verify basic arena partitioning + sizing
|
||||
{
|
||||
REQUIRE(g0 != g1);
|
||||
REQUIRE(gcos.new_space());
|
||||
REQUIRE(gcos.new_space() == gcos.to_space(g0));
|
||||
REQUIRE(gcos.new_space()->reserved() >= tc.gc_size_);
|
||||
REQUIRE(gcos.from_space(g0));
|
||||
REQUIRE(gcos.is_type_installed(typeseq::id<DList>()) == false);
|
||||
REQUIRE(gcos.is_type_installed(typeseq::id<DBoolean>()) == false);
|
||||
|
||||
for (Generation gi = g1; gi < tc.n_gen_; ++gi) {
|
||||
// all configured generations exist
|
||||
REQUIRE(gcos.to_space(gi));
|
||||
REQUIRE(gcos.from_space(gi));
|
||||
|
||||
// to- and from- space are distinct
|
||||
REQUIRE(gcos.to_space(gi) != gcos.from_space(gi));
|
||||
|
||||
// arenas for different generations are distinct
|
||||
for (Generation gj = g0; gj < gi; ++gj) {
|
||||
REQUIRE(gcos.to_space(gi) != gcos.to_space(gj));
|
||||
REQUIRE(gcos.from_space(gi) != gcos.to_space(gj));
|
||||
|
||||
REQUIRE(gcos.to_space(gi) != gcos.from_space(gj));
|
||||
REQUIRE(gcos.from_space(gi) != gcos.to_space(gj));
|
||||
}
|
||||
}
|
||||
|
||||
// generations that weren't requested, don't exist
|
||||
if (gn < c_max_generation) {
|
||||
REQUIRE(!gcos.to_space(gn));
|
||||
REQUIRE(!gcos.from_space(gn));
|
||||
}
|
||||
}
|
||||
gcos_install_test_types(tc, &gcos);
|
||||
gcos_verify_arena_partitioning(tc, gcos);
|
||||
|
||||
// verify we have non-zero space!
|
||||
{
|
||||
|
|
@ -411,6 +500,14 @@ namespace ut {
|
|||
std::vector<Recd> x1_v;
|
||||
std::vector<Recd> x2_v;
|
||||
{
|
||||
switch (tc.obj_graph_type_) {
|
||||
case TestGraphType::selfcycle:
|
||||
selfcycle_object_graph(&x1_v,
|
||||
&gcos,
|
||||
&x2_v,
|
||||
&arena2);
|
||||
break;
|
||||
case TestGraphType::random:
|
||||
random_object_graph(tc.n_test_obj_,
|
||||
tc.n_test_assign_,
|
||||
&rgen,
|
||||
|
|
@ -418,12 +515,32 @@ namespace ut {
|
|||
&gcos,
|
||||
&x2_v,
|
||||
&arena2);
|
||||
break;
|
||||
}
|
||||
|
||||
//x1_v.push_back(Recd(DBoolean::box(alloc, true),
|
||||
// sizeof(DBoolean),
|
||||
// typeseq::id<DBoolean>()));
|
||||
}
|
||||
|
||||
log1 && log1("verify before any gcos side effects");
|
||||
|
||||
{
|
||||
// traverses stored objects, updates counters
|
||||
// in verify_stats (= gco.p_verify_stats_, via ctor)
|
||||
//
|
||||
gcos.verify_ok(mock_gc_visitor);
|
||||
|
||||
INFO(tostr(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_)));
|
||||
|
||||
REQUIRE(verify_stats.is_ok());
|
||||
}
|
||||
|
||||
// someday: print the graph. Need a cycle-detecting printer
|
||||
|
||||
REQUIRE(x1_v.size() == x2_v.size());
|
||||
|
|
@ -502,7 +619,11 @@ namespace ut {
|
|||
const auto & x1 = x1_v.at(i);
|
||||
const auto & x2 = x2_v.at(i);
|
||||
|
||||
INFO(tostr(xtag("i", i), xtag("n", n), xtag("x1.tseq_", x1.tseq_)));
|
||||
log1 && log1("moving roots");
|
||||
log1 && log1(xtag("i", i),
|
||||
xtag("n", n),
|
||||
xtag("x1.tseq_", x1.tseq_),
|
||||
xtag("x1.tname", TypeRegistry::id2name(x1.tseq_)));
|
||||
|
||||
if (tc.do_type_registration_) {
|
||||
|
||||
|
|
@ -532,18 +653,24 @@ namespace ut {
|
|||
AGCObject * x1p_iface = gcos.lookup_type(x1.tseq_);
|
||||
REQUIRE(x1p_iface);
|
||||
|
||||
auto x1p_data = gcos.deep_move_root(mock_gc_visitor, x1.gco_, g1);
|
||||
obj<AGCObject> x1_gco = x1.gco_;
|
||||
|
||||
// modifies x1.gco_ in place
|
||||
auto x1p_data = gcos.deep_move_root(mock_gc_visitor,
|
||||
x1p_iface, (void **)&(x1.gco_.data_),
|
||||
g1);
|
||||
REQUIRE(x1p_data);
|
||||
REQUIRE(x1p_data == x1.gco_.data_);
|
||||
|
||||
obj<AGCObject> x1p_gco(x1p_iface, x1p_data);
|
||||
|
||||
// obj has been replaced by forwarding pointer to obj2
|
||||
// obj (x1_gco) now forwarding pointer to x1p_gco = x1.gco_
|
||||
{
|
||||
REQUIRE(gcos.contains_allocated(Role::from_space(), x1.gco_.data()));
|
||||
AllocInfo obj_info = gcos.alloc_info((std::byte *)x1.gco_.data());
|
||||
REQUIRE(gcos.contains_allocated(Role::from_space(), x1_gco.data()));
|
||||
AllocInfo obj_info = gcos.alloc_info((std::byte *)x1_gco.data());
|
||||
REQUIRE(obj_info.size() >= x1.alloc_z_);
|
||||
|
||||
REQUIRE(obj_info.payload().first == (std::byte *)x1.gco_.data());
|
||||
REQUIRE(obj_info.payload().first == (std::byte *)x1_gco.data());
|
||||
REQUIRE(obj_info.is_forwarding_tseq());
|
||||
}
|
||||
|
||||
|
|
@ -612,7 +739,10 @@ namespace ut {
|
|||
// but will fail since type isn't registered
|
||||
|
||||
auto x1p_data
|
||||
= gcos.deep_move_root(mock_gc_visitor, x1.gco_, g1);
|
||||
= gcos.deep_move_root(mock_gc_visitor,
|
||||
x1.gco_.iface(),
|
||||
(void **)&(x1.gco_.data_),
|
||||
g1);
|
||||
|
||||
// control here under normal GC use
|
||||
// would represent a configuration fail
|
||||
|
|
@ -625,22 +755,30 @@ namespace ut {
|
|||
// - deep_move_interior() // used from MutationLogStore
|
||||
// - forward_inplace_aux() // used from DX1Collector.visit_child
|
||||
|
||||
// - report_object_types
|
||||
// - report_object_ages()
|
||||
|
||||
{
|
||||
bool sanitize_flag = true;
|
||||
|
||||
// swaps to- and from- spaces again
|
||||
// Now from-space will be empty, all live objects in to-space
|
||||
|
||||
gcos.cleanup_phase(g1, sanitize_flag);
|
||||
}
|
||||
|
||||
#ifdef NOT_YET
|
||||
gcos.verify_ok(xxx objectvisitor,
|
||||
xxx &verify_stats);
|
||||
#endif
|
||||
{
|
||||
// traverses stored objects, updates counters
|
||||
// in verify_stats (= gco.p_verify_stats_, via ctor)
|
||||
//
|
||||
gcos.verify_ok(mock_gc_visitor);
|
||||
|
||||
// - verify_ok
|
||||
INFO(tostr(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_)));
|
||||
|
||||
REQUIRE(verify_stats.is_ok());
|
||||
}
|
||||
|
||||
{
|
||||
obj<AGCObject> report_gco;
|
||||
|
|
@ -662,8 +800,8 @@ namespace ut {
|
|||
bool ok = gcos.report_object_ages(report_mm, error_mm, &report_gco);
|
||||
|
||||
if (!ok) {
|
||||
log.retroactively_enable();
|
||||
log && log(xtag("error", report_mm.last_error()));
|
||||
log1.retroactively_enable();
|
||||
log1 && log1(xtag("error", report_mm.last_error()));
|
||||
}
|
||||
|
||||
REQUIRE(ok);
|
||||
|
|
@ -671,11 +809,13 @@ namespace ut {
|
|||
|
||||
// TODO: print report_gco, verify output
|
||||
|
||||
// discard report
|
||||
|
||||
report_gco.reset();
|
||||
report_mm->clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
} /* loop over test cases */
|
||||
} /* TEST_CASE(GCObjectStore-1) */
|
||||
|
||||
} /*namespace ut*/
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue