xo-alloc / xo-object: utest coverage + assorted bugfixes

This commit is contained in:
Roland Conybeare 2025-08-07 18:32:14 -05:00
commit a6e4430825
21 changed files with 861 additions and 284 deletions

View file

@ -15,36 +15,20 @@ namespace xo {
*
* @text
*
* before @ref release_redline_memory
*
* <-----allocated----> <-free-> <-reserved->
* XXXXXXXXXXXXXXXXXXXX______________________
* ^ ^ ^ ^
* lo free redline hi
* limit
*
* after @ref release_redline_memory
*
* <-----allocated----> <--------free------->
* XXXXXXXXXXXXXXXXXXXX______________________
* ^ ^ ^
* lo free hi
* limit
* @endtext
*
* TODO: rename to ArenaAlloc
**/
class ArenaAlloc : public IAlloc {
public:
~ArenaAlloc();
/** create allocator with capacity @p z,
* with reserved capacity @p redline_z.
**/
static up<ArenaAlloc> make(const std::string & name,
#ifdef REDLINE_MEMORY
std::size_t redline_z,
#endif
std::size_t z,
bool debug_flag);
@ -56,7 +40,7 @@ namespace xo {
// inherited from IAlloc...
virtual const std::string & name() const final override { return name_; }
virtual const std::string & name() const final override;
virtual std::size_t size() const final override;
virtual std::size_t available() const final override;
virtual std::size_t allocated() const final override;
@ -64,19 +48,14 @@ namespace xo {
virtual bool is_before_checkpoint(const void * x) const final override;
virtual std::size_t before_checkpoint() const final override;
virtual std::size_t after_checkpoint() const final override;
virtual bool debug_flag() const final override;
virtual void clear() final override;
virtual void checkpoint() final override;
virtual std::byte * alloc(std::size_t z) final override;
#ifdef REDLINE_MEMORY
virtual void release_redline_memory() final override;
#endif
private:
ArenaAlloc(const std::string & name,
#ifdef REDLINE_MEMORY
std::size_t rz,
#endif
std::size_t z, bool debug_flag);
private:
@ -99,10 +78,6 @@ namespace xo {
std::byte * free_ptr_ = nullptr;
/** soft limit: end of released memory **/
std::byte * limit_ = nullptr;
#ifdef REDLINE_MEMORY
/** amount of last-resort memory to reserve **/
std::size_t redline_z_ = 0;
#endif
/** hard limit: end of allocated memory **/
std::byte * hi_ = nullptr;
/** true to enable detailed debug logging **/

View file

@ -199,6 +199,15 @@ namespace xo {
#endif
private:
ListAlloc * nursery_to() const { return nursery(role::to_space); }
ListAlloc * nursery_from() const { return nursery(role::from_space); }
ListAlloc * tenured_to() const { return tenured(role::to_space); }
ListAlloc * tenured_from() const { return tenured(role::from_space); }
ListAlloc * nursery(role r) const { return nursery_[role2int(r)].get(); }
ListAlloc * tenured(role r) const { return tenured_[role2int(r)].get(); }
/** begin GC now **/
void execute_gc(generation g);
/** cleanup phase. aux function for @ref execute_gc **/

View file

@ -52,7 +52,7 @@ namespace xo {
/** number of bytes allocated since @ref checkpoint **/
virtual std::size_t after_checkpoint() const = 0;
/** @return true iff debug logging enabled **/
virtual bool debug_flag() const { return false; }
virtual bool debug_flag() const = 0;
/** reset allocator to empty state. **/
virtual void clear() = 0;
@ -76,10 +76,6 @@ namespace xo {
* Only used in @ref GC. Default implementation asserts and returns nullptr
**/
virtual std::byte * alloc_gc_copy(std::size_t z, const void * src);
#ifdef REDLINE_MEMORY
/** release last-resort reserved memory **/
virtual void release_redline_memory() = 0;
#endif
};
} /*namespace gc*/

View file

@ -29,9 +29,6 @@ namespace xo {
ListAlloc(std::unique_ptr<ArenaAlloc> hd,
ArenaAlloc * marked,
std::size_t cz, std::size_t nz, std::size_t tz,
#ifdef REDLINE_MEMORY
bool use_redline,
#endif
bool debug_flag);
~ListAlloc();
@ -40,8 +37,8 @@ namespace xo {
/** reset to have at least @p z bytes of storage **/
bool reset(std::size_t z);
/** expand bucket list to accomodate a requrest of size @p z **/
bool expand(std::size_t z);
/** expand bucket list to accomodate a request of size @p z **/
bool expand(std::size_t z, const std::string & name);
/** current free pointer **/
std::byte * free_ptr() const;
@ -64,13 +61,11 @@ namespace xo {
virtual bool is_before_checkpoint(const void * x) const final override;
virtual std::size_t before_checkpoint() const final override;
virtual std::size_t after_checkpoint() const final override;
virtual bool debug_flag() const final override;
virtual void clear() final override;
virtual void checkpoint() final override;
virtual std::byte * alloc(std::size_t z) final override;
#ifdef REDLINE_MEMORY
virtual void release_redline_memory() final override;
#endif
private:
/** **/
@ -89,11 +84,6 @@ namespace xo {
std::size_t next_z_ = 0;
/** total size of @ref hd_ + contents of @ref full_l_ **/
std::size_t total_z_ = 0;
#ifdef REDLINE_MEMORY
bool use_redline_ = false;
bool redlined_flag_ = false;
#endif
/** true to enable debug logging **/
bool debug_flag_ = false;
};

View file

@ -3,6 +3,9 @@
* author: Roland Conybeare, Aug 2025
*/
#pragma once
#include <ostream>
#include <cstdint>
namespace xo {
@ -15,6 +18,13 @@ namespace xo {
constexpr std::size_t gen2int(generation x) { return static_cast<std::size_t>(x); }
const char * gen2str(generation x);
inline std::ostream & operator<<(std::ostream & os, generation x) {
os << gen2str(x);
return os;
}
enum class generation_result {
nursery,
tenured,

View file

@ -13,36 +13,19 @@
namespace xo {
namespace gc {
ArenaAlloc::ArenaAlloc(const std::string & name,
#ifdef REDLINE_MEMORY
std::size_t rz,
#endif
std::size_t z, bool debug_flag)
{
this->name_ = name;
#ifdef REDLINE_MEMORY
this->lo_ = (new std::byte [rz + z]);
#else
this->lo_ = (new std::byte [z]);
#endif
this->checkpoint_ = lo_;
this->free_ptr_ = lo_;
this->limit_ = lo_ + z;
#ifdef REDLINE_MEMORY
this->redline_z_ = rz;
this->hi_ = limit_ + rz;
#else
this->hi_ = limit_;
#endif
this->debug_flag_ = debug_flag;
if (!lo_) {
#ifdef REDLINE_MEMORY
throw std::runtime_error(tostr("ArenaAlloc: allocation failed",
xtag("size", rz + z)));
#else
throw std::runtime_error(tostr("ArenaAlloc: allocation failed",
xtag("size", z)));
#endif
}
}
@ -56,24 +39,15 @@ namespace xo {
this->checkpoint_ = nullptr;
this->free_ptr_ = nullptr;
this->limit_ = nullptr;
#ifdef REDLINE_MEMORY
this->redline_z_ = 0;
#endif
this->hi_ = nullptr;
this->debug_flag_ = false;
}
up<ArenaAlloc>
ArenaAlloc::make(const std::string & name,
#ifdef REDLINE_MEMORY
std::size_t rz,
#endif
std::size_t z, bool debug_flag)
{
return up<ArenaAlloc>(new ArenaAlloc(name,
#ifdef REDLINE_MEMORY
rz,
#endif
z, debug_flag));
}
@ -97,22 +71,36 @@ namespace xo {
ArenaAlloc::capture_object_statistics(capture_phase phase,
ObjectStatistics * p_dest) const
{
scope log(XO_DEBUG(debug_flag_),
xtag("name", name_),
xtag("capacity", limit_ - lo_),
xtag("alloc", free_ptr_ - lo_),
xtag("lo", (void*)lo_),
xtag("free_ptr", (void*)free_ptr_));
using xo::reflect::TaggedPtr;
std::byte * p = lo_;
while (p < free_ptr_) {
Object * obj = reinterpret_cast<Object *>(p);
TaggedPtr tp = obj->self_tp();
std::size_t z = obj->_shallow_size();
Object * obj = reinterpret_cast<Object *>(p);
TaggedPtr tp = obj->self_tp();
std::size_t z = obj->_shallow_size();
std::uint32_t id = tp.td()->id().id();
log && log(xtag("obj", (void*)obj),
xtag("z", z),
xtag("typeid", id));
if (p_dest->per_type_stats_v_.size() < id + 1)
p_dest->per_type_stats_v_.resize(id + 1);
PerObjectTypeStatistics & dest = p_dest->per_type_stats_v_.at(id);
dest.td_ = tp.td();
log && log(xtag("td", tp.td()->short_name()));
switch (phase) {
case capture_phase::sab:
++dest.scanned_n_;
@ -130,6 +118,11 @@ namespace xo {
assert(p == free_ptr_);
}
const std::string &
ArenaAlloc::name() const {
return name_;
}
std::size_t
ArenaAlloc::size() const {
return limit_ - lo_;
@ -167,15 +160,17 @@ namespace xo {
return free_ptr_ - checkpoint_;
}
bool
ArenaAlloc::debug_flag() const
{
return debug_flag_;
}
void
ArenaAlloc::clear()
{
this->set_free_ptr(lo_);
#ifdef REDLINE_MEMORY
this->limit_ = hi_ - redline_z_;
#else
this->limit_ = hi_;
#endif
}
void
@ -204,7 +199,7 @@ namespace xo {
std::byte * retval = this->free_ptr_;
log && log(xtag("self", name_), xtag("z0", z0), xtag("+pad", dz), xtag("z1", z1));
log && log(xtag("self", name_), xtag("z0", z0), xtag("+pad", dz), xtag("z1", z1), xtag("avail", this->available()));
if (free_ptr_ + z1 > limit_) {
return nullptr;
@ -215,13 +210,6 @@ namespace xo {
return retval;
}
#ifdef REDLINE_MEMORY
void
ArenaAlloc::release_redline_memory() {
this->limit_ = this->hi_;
}
#endif
} /*namespace gc*/
} /*namespace xo*/

View file

@ -10,6 +10,7 @@ set(SELF_SRCS
ObjectStatistics.cpp
Object.cpp
Forwarding1.cpp
generation.cpp
)
xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS})

View file

@ -26,7 +26,7 @@ namespace xo {
void
Forwarding1::display(std::ostream & os) const
{
os << "<fwd" << xtag("dest", (void*)dest_.ptr()) << ">";
os << "<fwd" << xtag("dest-td", dest_->self_tp().td()->short_name()) << ">";
}
Object *

View file

@ -175,7 +175,8 @@ namespace xo {
GcStatisticsExt retval = GcStatisticsExt(this->native_gc_statistics());
retval.nursery_z_ = nursery_[role2int(role::to_space)]->size();
retval.nursery_before_checkpoint_z_ = nursery_[role2int(role::to_space)]->before_checkpoint();
retval.nursery_before_checkpoint_z_ = this->nursery_to()->before_checkpoint();
retval.nursery_after_checkpoint_z_ = this->nursery_to()->after_checkpoint();
retval.tenured_z_ = tenured_[role2int(role::to_space)]->size();
return retval;
@ -254,21 +255,9 @@ namespace xo {
{
std::byte * x = nursery_[role2int(role::to_space)]->alloc(z);
if (!x) {
/* ListAlloc won't fail -- instead will increase heap size */
/* ListAlloc won't fail unless we exhaust memory -- instead will increase heap size */
this->request_gc(generation::nursery);
#ifdef REDLINE_MEMORY
if (incr_gc_pending_ || full_gc_pending_)
nursery_[role2int(role::to_space)]->release_redline_memory();
/* try (just once) more, maybe request fits in redline space */
x = nursery_[role2int(role::to_space)]->alloc(z);
#endif
assert(x);
}
assert(x);
return x;
}
@ -308,18 +297,6 @@ namespace xo {
log && log("nursery");
retval = nursery_[role2int(role::to_space)]->alloc(z);
if (!retval) {
/* nursery space exhausted !? */
this->request_gc(generation::nursery);
#ifdef REDLINE_MEMORY
nursery_[role2int(role::to_space)]->release_redline_memory();
#endif
retval = nursery_[role2int(role::to_space)]->alloc(z);
}
}
}
break;
@ -393,14 +370,6 @@ namespace xo {
}
}
#ifdef REDLINE_MEMORY
void
GC::release_redline_memory()
{
// not supported feature for GC
}
#endif
void
GC::swap_nursery()
{
@ -428,40 +397,62 @@ namespace xo {
void
GC::swap_spaces(generation target)
{
scope log(XO_DEBUG(this->debug_flag()));
scope log(XO_DEBUG(this->debug_flag()), xtag("upto", target));
// will be copying into the memory regions currently labelled FromSpace
/* gc will copy some to-be-determined amount in [0..promote_z]
from nursery->tenured generation.
*/
std::size_t promote_z = nursery_[role2int(role::to_space)]->before_checkpoint();
std::size_t max_promote_z = nursery_[role2int(role::to_space)]->before_checkpoint();
log && log(xtag("max_promote_z", max_promote_z));
if (target == generation::tenured) {
/* gc on tenured generation may need this much space */
std::size_t tenured_z = (tenured_[role2int(role::to_space)]->allocated()
+ promote_z
+ full_gc_threshold_);
std::size_t need_tenured_z = (tenured_[role2int(role::to_space)]->allocated()
+ max_promote_z
+ full_gc_threshold_);
tenured_[role2int(role::from_space)]->reset(tenured_z);
log && log("need_tenured_z", need_tenured_z);
tenured_from()->reset(need_tenured_z);
this->swap_tenured();
} else {
if (tenured_[role2int(role::to_space)]->available() < promote_z) {
tenured_[role2int(role::to_space)]->expand(promote_z);
std::size_t avail_tenured_z = tenured_[role2int(role::to_space)]->available();
log && log(xtag("avail_tenured_z", avail_tenured_z));
if (avail_tenured_z < max_promote_z) {
ListAlloc * tenured_to = this->tenured_to();
tenured_to->expand(max_promote_z, tenured_to->name() + "+");
}
}
nursery_[role2int(role::from_space)]->reset(nursery_[role2int(role::to_space)]->allocated()
- promote_z
+ incr_gc_threshold_);
/* subtracting max_promote_z is correct here, since anything not promoted is garbage */
std::size_t need_nursery_z = (nursery(role::to_space)->allocated()
- max_promote_z
+ incr_gc_threshold_);
log && log(xtag("need_nursery_z", need_nursery_z));
/* (from-space is about to become to-space, to receive surviving nursery objects) */
nursery(role::from_space)->reset(need_nursery_z);
this->swap_nursery();
this->swap_mutation_log();
log && log(xtag("nursery.from", nursery_[role2int(role::from_space)]->name()));
log && log(xtag("nursery.to", nursery_[role2int(role::to_space) ]->name()));
log && log(xtag("tenured.from", tenured_[role2int(role::from_space)]->name()));
log && log(xtag("tenured.to", tenured_[role2int(role::to_space) ]->name()));
ListAlloc * N_from = nursery(role::from_space);
log && log(xtag("nursery.from", N_from->name()), xtag("size", N_from->size()));
ListAlloc * N_to = nursery(role::to_space);
log && log(xtag("nursery.to", N_to->name()), xtag("size", N_to->size()));
ListAlloc * T_from = tenured(role::from_space);
log && log(xtag("tenured.from", T_from->name()), xtag("size", T_from->size()));
ListAlloc * T_to = tenured(role::to_space);
log && log(xtag("tenured.to", T_to->name()), xtag("size", T_to->size()));
} /*swap_spaces*/
@ -504,8 +495,12 @@ namespace xo {
void
GC::copy_globals(generation upto)
{
scope log(XO_DEBUG(config_.debug_flag_),
xtag("roots", gc_root_v_.size()));
for (Object ** pp_root : gc_root_v_) {
this->copy_object(pp_root, upto, &object_statistics_sae_[gen2int(upto)]);
this->copy_object(pp_root, upto,
&object_statistics_sae_[gen2int(upto)]);
}
}
@ -750,28 +745,30 @@ namespace xo {
{
scope log(XO_DEBUG(config_.debug_flag_));
std::size_t N_allocated = nursery_[role2int(role::from_space)]->after_checkpoint();
std::size_t T_allocated = tenured_[role2int(role::from_space)]->after_checkpoint();
std::size_t N_allocated = nursery_from()->after_checkpoint();
std::size_t T_allocated = tenured_from()->after_checkpoint();
std::size_t N_before_gc = nursery_[role2int(role::from_space)]->allocated();
std::size_t T_before_gc = tenured_[role2int(role::from_space)]->allocated();
std::size_t N_before_gc = nursery_from()->allocated();
std::size_t T_before_gc = tenured_from()->allocated();
std::size_t N_after_gc = nursery_[role2int(role::to_space)]->allocated();
std::size_t T_after_gc = tenured_[role2int(role::to_space)]->allocated();
std::size_t N_after_gc = nursery_to()->allocated();
std::size_t T_after_gc = tenured_to()->allocated();
//std::byte * N_free_ptr = nursery_[role2int(role::to_space)]->free_ptr();
std::size_t promote_z = gc_statistics_.total_promoted_ - gc_statistics_.total_promoted_sab_;
std::size_t promote_z = (gc_statistics_.total_promoted_
- gc_statistics_.total_promoted_sab_);
this->nursery_[role2int(role::from_space)]->reset(0);
this->tenured_[role2int(role::from_space)]->reset(0);
/* Don't reset from-space here, it's unnecessary.
* Would be permissible, but interferes with GC object modelling in
* xo-object/utest/GC.test.cpp
*/
//this->nursery_[role2int(role::from_space)]->reset(0);
//this->tenured_[role2int(role::from_space)]->reset(0);
/* objects currenty in to-space nursery have survived one collection */
this->nursery_[role2int(role::to_space)]->checkpoint();
// nursery_[role2int(role::to_space)]->set_redline(nursery_[role2int(role::to_space)]->allocated() + incr_gc_threshold_)
this->nursery_to()->checkpoint();
if (upto == generation::tenured)
this->tenured_[role2int(role::to_space)]->checkpoint();
this->tenured_to()->checkpoint();
if (log) {
log(xtag("N_allocated", N_allocated));
@ -819,7 +816,7 @@ namespace xo {
this->capture_object_statistics(upto, capture_phase::sab);
log && log("step 1 : swap to/from roles");
log && log("step 1 : swap to/from roles");
this->swap_spaces(upto);
@ -829,15 +826,15 @@ namespace xo {
log && log("step 2b: TODO: copy pinned");
log && log("step 3 : forward mutation log");
log && log("step 3 : forward mutation log");
this->forward_mutation_log(upto);
log && log("step 4 : TODO: notify destructor log");
log && log("step 4 : TODO: notify destructor log");
log && log("step 5 : TODO: keep reachable weak pointers");
log && log("step 5 : TODO: keep reachable weak pointers");
log && log("step 6 : cleanup");
log && log("step 6 : cleanup");
this->cleanup_phase(upto);

View file

@ -32,12 +32,12 @@ namespace xo {
PerGenerationStatistics::display(std::ostream & os) const
{
os << "<PerGenerationStatistics"
<< rtag("used", used_z_)
<< rtag("n_gc", n_gc_)
<< rtag("new_alloc_z", new_alloc_z_)
<< rtag("scanned_z", scanned_z_)
<< rtag("survive_z", survive_z_)
<< rtag("promote_z", promote_z_)
<< xrtag("used", used_z_)
<< xrtag("n_gc", n_gc_)
<< xrtag("new_alloc_z", new_alloc_z_)
<< xrtag("scanned_z", scanned_z_)
<< xrtag("survive_z", survive_z_)
<< xrtag("promote_z", promote_z_)
<< ">";
}
@ -62,9 +62,9 @@ namespace xo {
GcStatistics::display(std::ostream & os) const
{
os << "<GcStatistics"
<< rtag("gen_v", gen_v_)
<< rtag("total_allocated", total_allocated_)
<< rtag("total_promoted_sab", total_promoted_sab_)
<< xrtag("gen_v", gen_v_)
<< xrtag("total_allocated", total_allocated_)
<< xrtag("total_promoted_sab", total_promoted_sab_)
// total_promoted
// n_mtuation
// n_logged_mutation
@ -78,17 +78,17 @@ namespace xo {
GcStatisticsExt::display(std::ostream & os) const
{
os << "<GcStatisticsExt"
<< rtag("gen_v", gen_v_)
<< rtag("total_allocated", total_allocated_)
<< rtag("total_promoted_sab", total_promoted_)
<< rtag("nursery_z", nursery_z_)
<< rtag("nursery_before_ckp_z", nursery_before_checkpoint_z_)
<< rtag("nursery_after_ckp_z", nursery_after_checkpoint_z_)
<< rtag("tenured_z", tenured_z_)
<< rtag("n_mutation", n_mutation_)
<< rtag("n_logged_mutation", n_logged_mutation_)
<< rtag("n_xgen_mutation", n_xgen_mutation_)
<< rtag("n_xkcp_mutation", n_xckp_mutation_)
<< xrtag("gen_v", gen_v_)
<< xrtag("total_allocated", total_allocated_)
<< xrtag("total_promoted_sab", total_promoted_)
<< xrtag("nursery_z", nursery_z_)
<< xrtag("nursery_before_ckp_z", nursery_before_checkpoint_z_)
<< xrtag("nursery_after_ckp_z", nursery_after_checkpoint_z_)
<< xrtag("tenured_z", tenured_z_)
<< xrtag("n_mutation", n_mutation_)
<< xrtag("n_logged_mutation", n_logged_mutation_)
<< xrtag("n_xgen_mutation", n_xgen_mutation_)
<< xrtag("n_xckp_mutation", n_xckp_mutation_)
// << xtag("per_type_stats", per_type_stats_)
<< ">";
}

View file

@ -5,6 +5,7 @@
#include "ListAlloc.hpp"
#include "ArenaAlloc.hpp"
#include "xo/indentlog/scope.hpp"
#include <cassert>
#include <cstddef>
@ -13,9 +14,6 @@ namespace xo {
ListAlloc::ListAlloc(std::unique_ptr<ArenaAlloc> hd,
ArenaAlloc * marked,
std::size_t cz, std::size_t nz, std::size_t tz,
#ifdef REDLINE_MEMORY
bool use_redline,
#endif
bool debug_flag)
: start_z_{cz},
hd_{std::move(hd)},
@ -24,9 +22,6 @@ namespace xo {
current_z_{cz},
next_z_{nz},
total_z_{tz},
#ifdef REDLINE_MEMOORY
use_redline_{use_redline},
#endif
debug_flag_{debug_flag}
{}
@ -39,9 +34,6 @@ namespace xo {
ListAlloc::make(const std::string & name, std::size_t cz, std::size_t nz, bool debug_flag)
{
std::unique_ptr<ArenaAlloc> hd{ArenaAlloc::make(name,
#ifdef REDLINE_MEMORY
0,
#endif
cz, debug_flag)};
if (!hd)
@ -52,9 +44,6 @@ namespace xo {
up<ListAlloc> retval{new ListAlloc(std::move(hd),
marked,
cz, nz, cz,
#ifdef REDLINE_MEMORY
false /*!use_redline*/,
#endif
debug_flag)};
return retval;
@ -134,34 +123,31 @@ namespace xo {
bool
ListAlloc::is_before_checkpoint(const void * x) const {
if (!marked_)
return false;
return true;
if ((marked_ == hd_.get()) && hd_->contains(x))
return hd_->is_before_checkpoint(x);
if (marked_ && marked_->contains(x))
return marked_->is_before_checkpoint(x);
/*
* 1. allocs in full_l_ appear in youngest-to-oldest order
* 2. allocators that appear before marked_ in full_l_ count as 'after checkpoint'
* 3. allocators that appear after marked_ in full_l_ count as 'before checkpoint'
* 1. allocs in full_l_ appear in oldest-to-youngest order
* 2. allocators that appear before marked_ in full_l_ count as 'before checkpoint'
* 3. allocators that appear after marked_ in full_l_ count as 'after checkpoint'
*/
bool younger_than_marked = true;
bool older_than_marked = true;
for (const auto & alloc : full_l_) {
if (younger_than_marked) {
if (older_than_marked) {
if (alloc.get() == marked_) {
/* nothing else to test on this iteration,
* already checked .marked_ specifically
*/
younger_than_marked = false;
break;
} else {
/* after checkpoint */
/* before checkpoint */
if (alloc->contains(x))
return false;
return true;
}
} else {
if (alloc->contains(x))
return true;
}
}
@ -171,55 +157,63 @@ namespace xo {
std::size_t
ListAlloc::before_checkpoint() const
{
scope log(XO_DEBUG(false && debug_flag_), xtag("marked", marked_ ? marked_->name() : ""));
if (marked_) {
if (full_l_.empty()) {
assert(marked_ == hd_.get());
return marked_->before_checkpoint();
} else {
std::size_t z = 0;
/* control here: .marked & .full_l non-empty. */
if (hd_.get() == marked_) {
z += hd_->before_checkpoint();
/* anything in .full_l is older than marked .hd */
for (const auto & alloc : full_l_) {
z += alloc->allocated();
}
return z;
} else {
/* messiest case: .marked is true,
* and not the youngest arena
*/
/* full_l always in increasing time order: oldest-to-youngest order */
size_t i_alloc = 0;
for (const auto & alloc : full_l_) {
log && log(xtag("i_alloc", i_alloc),
xtag("alloc", alloc->name()),
xtag("z", z));
if (alloc.get() == marked_) {
log && log("marked", xtag("+z", marked_->before_checkpoint()));
z += marked_->before_checkpoint();
break;
} else {
log && log("older than marked", xtag("+z", alloc->allocated()));
z += alloc->allocated();
}
++i_alloc;
}
}
return z;
}
} else {
/* count everything allocated */
/* count *everything* allocated */
return this->allocated();
}
std::size_t z = 0;
/* control here: .marked & .full_l non-empty. */
if (hd_.get() == marked_) {
z += hd_->before_checkpoint();
/* anything in .full_l older than marked .hd */
for (const auto & alloc : full_l_) {
z += alloc->allocated();
}
return z;
} else {
/* messiest case: .marked is true,
* and not the youngest arena
*/
bool younger_than_marked = true;
for (const auto & alloc : full_l_) {
if (younger_than_marked) {
if (alloc.get() == marked_) {
younger_than_marked = false;
z += marked_->before_checkpoint();
} else {
;
}
} else {
z += alloc->allocated();
}
}
}
return z;
}
std::size_t
ListAlloc::after_checkpoint() const
{
scope log(XO_DEBUG(false && debug_flag_), xtag("marked", marked_ ? marked_->name() : ""));
if (!marked_)
return 0;
@ -229,25 +223,44 @@ namespace xo {
return marked_->after_checkpoint();
}
bool younger_than_marked = true;
bool older_than_marked = true;
std::size_t z = 0;
std::size_t i_alloc = 0;
for (const auto & alloc : full_l_) {
if (younger_than_marked) {
log && log(xtag("i_alloc", i_alloc),
xtag("alloc", alloc->name()),
xtag("z", z));
if (older_than_marked) {
if (alloc.get() == marked_) {
younger_than_marked = false;
log && log("marked", xtag("+z", marked_->after_checkpoint()));
older_than_marked = false;
z += marked_->after_checkpoint();
break;
} else {
z += alloc->allocated();
}
} else {
/* younger than marked */
log && log("younger", xtag("+z", alloc->allocated()));
z += alloc->allocated();
}
++i_alloc;
}
/** head must be included, since it's always the youngest bucket **/
z += hd_->after_checkpoint();
log && log("z", z);
return z;
}
bool
ListAlloc::debug_flag() const {
return debug_flag_;
}
void
ListAlloc::clear() {
// general hygiene
@ -258,26 +271,17 @@ namespace xo {
current_z_ = 0;
next_z_ = 0;
total_z_ = 0;
#ifdef REDLINE_MEMORY
use_redline_ = false;
#endif
}
bool
ListAlloc::reset(std::size_t z)
{
#ifdef REDLINE_MEMORY
// warning: hd_->size() does not include redline memory
hd_->release_redline_memory();
#endif
scope log(XO_DEBUG(debug_flag_), xtag("z", z));
bool recycle_head_bucket = hd_ && (z <= hd_->size());
this->full_l_.clear();
this->marked_ = nullptr;
#ifdef REDLINE_MEMORY
this->redlined_flag_ = false;
#endif
if (recycle_head_bucket) {
this->hd_->clear();
@ -285,16 +289,22 @@ namespace xo {
return true;
} else {
std::string old_name = this->hd_->name();
this->hd_.reset(nullptr);
this->total_z_ = 0;
return this->expand(z);
return this->expand(z, old_name + "+");
}
}
bool
ListAlloc::expand(std::size_t z)
ListAlloc::expand(std::size_t z, const std::string & name)
{
scope log(XO_DEBUG(debug_flag_), xtag("name", name));
//log && log("before", xtag("before_ckp", this->before_checkpoint()));
std::size_t cz = current_z_;
std::size_t nz = next_z_;
std::size_t tz;
@ -305,12 +315,9 @@ namespace xo {
nz = tz;
} while (cz < z);
std::string name = hd_->name() + "+exp";
log && log("expand to", xtag("cz", cz));
std::unique_ptr<ArenaAlloc> new_alloc = ArenaAlloc::make(name,
#ifdef REDLINE_MEMORY
0,
#endif
cz, debug_flag_);
if (!new_alloc)
@ -320,41 +327,43 @@ namespace xo {
this->next_z_ = nz;
this->total_z_ += cz;
if (hd_)
this->full_l_.push_back(std::move(hd_));
this->hd_ = std::move(new_alloc);
//log && log("after", xtag("before_ckp", this->before_checkpoint()));
return true;
}
void
ListAlloc::checkpoint() {
scope log(XO_DEBUG(debug_flag_));
hd_->checkpoint();
this->marked_ = hd_.get();
log && log(xtag("hd", (void*)hd_.get()), xtag("marked", (void*)marked_));
}
std::byte *
ListAlloc::alloc(std::size_t z) {
scope log(XO_DEBUG(debug_flag_));
std::byte * retval = hd_->alloc(z);
if (retval)
return retval;
if (this->expand(z))
log && log("space exhausted -> expand");
if (this->expand(z, hd_->name() + "+"))
return hd_->alloc(z);
return nullptr;
}
#ifdef REDLINE_MEMORY
void
ListAlloc::release_redline_memory()
{
if (use_redline_)
redlined_flag_ = true;
this->hd_->release_redline_memory();
}
#endif
} /*namespace gc*/
} /*namespace xo*/

View file

@ -14,13 +14,13 @@ namespace xo {
{
os << "<PerObjectTypeStatistics";
if (td_)
os << rtag("td", td_->short_name());
os << xrtag("td", td_->short_name());
else
os << rtag("td", "nullptr");
os << rtag("scanned_n", scanned_n_)
<< rtag("scanned_z", scanned_z_)
<< rtag("survive_n", survive_n_)
<< rtag("survive_z", survive_z_)
os << xrtag("td", "nullptr");
os << xrtag("scanned_n", scanned_n_)
<< xrtag("scanned_z", scanned_z_)
<< xrtag("survive_n", survive_n_)
<< xrtag("survive_z", survive_z_)
<< ">";
}

22
src/alloc/generation.cpp Normal file
View file

@ -0,0 +1,22 @@
/* generation.cpp
*
* author: Roland Conybeare, Aug 2025
*/
#include "generation.hpp"
namespace xo {
namespace gc {
const char * gen2str(generation x) {
switch (x) {
case generation::nursery: return "nursery";
case generation::tenured: return "tenured";
case generation::N: break;
}
return "?generation";
}
} /*namespace gc*/
} /*namespace xo*/
/* generation.cpp */

View file

@ -15,14 +15,8 @@ namespace xo {
struct testcase_alloc {
testcase_alloc(std::size_t rz, std::size_t z)
:
#ifdef REDLINE_MEMORY
redline_z_{rz},
#endif
arena_z_{z} {}
#ifdef REDLINE_MEMORY
std::size_t redline_z_;
#endif
std::size_t arena_z_;
};
@ -42,9 +36,6 @@ namespace xo {
constexpr bool c_debug_flag = false;
auto alloc = ArenaAlloc::make("linearalloc",
#ifdef REDLINE_MEMORY
tc.redline_z_,
#endif
tc.arena_z_, c_debug_flag);
REQUIRE(alloc.get());

View file

@ -7,7 +7,13 @@ set(UTEST_SRCS
alloc_utest_main.cpp
IAlloc.test.cpp
ArenaAlloc.test.cpp
GC.test.cpp)
ListAlloc.test.cpp
GC.test.cpp
GcStatistics.test.cpp
ObjectStatistics.test.cpp
Forwarding1.test.cpp
generation.test.cpp
)
xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS})
xo_self_dependency(${UTEST_EXE} xo_alloc)

View file

@ -0,0 +1,85 @@
/* Forwarding1.test.cpp
*
* author: Roland Conybeare, Aug 2025
*/
#include "Forwarding1.hpp"
#include "ArenaAlloc.hpp"
#include "xo/reflect/Reflect.hpp"
#include <catch2/catch.hpp>
#include <cstring>
namespace xo {
using xo::reflect::Reflect;
using xo::obj::Forwarding1;
namespace gc {
namespace {
class DummyObject : public Object {
public:
explicit DummyObject(const char * data) {
::strncpy(data_.data(), data, 128);
}
gp<Object> member() const { return member_; }
void assign_member(Object * x) {
Object::mm->assign_member(this, member_.ptr_address(), x);
}
TaggedPtr self_tp() const final override {
return Reflect::make_tp(const_cast<DummyObject*>(this));
}
void display(std::ostream & os) const final override { os << data_; }
virtual std::size_t _shallow_size() const final override { return sizeof(*this); }
virtual Object * _shallow_copy() const final override { return new (Cpof(this)) DummyObject(*this); }
virtual std::size_t _forward_children() final override { return _shallow_size(); }
private:
std::array<char, 128> data_;
gp<Object> member_;
};
}
TEST_CASE("Forwarding1", "[gc][alloc]")
{
bool saved = tag_config::tag_color_enabled;
tag_config::tag_color_enabled = false;
gp<Object> obj = new DummyObject("Well, I wasn't expecting that!");
gp<Forwarding1> fwd = new Forwarding1(obj);
REQUIRE(fwd->_destination() == obj.ptr());
REQUIRE(fwd->_offset_destination(fwd.ptr()) == obj.ptr());
REQUIRE(fwd->self_tp().td()->short_name() == "Forwarding1");
std::stringstream ss;
ss << fwd;
REQUIRE(ss.str() == "<fwd :dest-td DummyObject>");
tag_config::tag_color_enabled = saved;
}
TEST_CASE("IAlloc.assign_member", "[gc][alloc]")
{
/* not giving this nit it's own translation unit.
*/
gp<DummyObject> obj = new DummyObject("This also a surprise..");
up<ArenaAlloc> arena = ArenaAlloc::make("test", 1024, false);
Object::mm = arena.get();
obj->assign_member(obj.ptr());
REQUIRE(obj->member().ptr() == obj.ptr());
}
} /*namespace gc*/
} /*namespace xo*/
/* end Forwarding1.test.cpp */

View file

@ -37,6 +37,7 @@ namespace xo {
.initial_tenured_z_ = tc.tenured_z_});
REQUIRE(gc.get());
REQUIRE(gc->name() == "GC");
REQUIRE(gc->size() == tc.nursery_z_ + tc.tenured_z_);
REQUIRE(gc->allocated() == 0);
REQUIRE(gc->available() == tc.nursery_z_);

216
utest/GcStatistics.test.cpp Normal file
View file

@ -0,0 +1,216 @@
/* @file GcStatistics.test.cpp
*
* author: Roland Conybeare, Aug 2025
*/
#include "xo/alloc/GcStatistics.hpp"
#include "xo/indentlog/scope.hpp"
#include "xo/indentlog/print/tostr.hpp"
#include "xo/indentlog/print/hex.hpp"
#include <catch2/catch.hpp>
#include <ranges>
namespace xo {
using xo::gc::GcStatistics;
using xo::gc::GcStatisticsExt;
using xo::gc::PerGenerationStatistics;
using xo::print::ppconfig;
namespace ut {
TEST_CASE("PerGenerationStatistics", "[alloc][gc]")
{
bool saved = tag_config::tag_color_enabled;
tag_config::tag_color_enabled = false;
PerGenerationStatistics stats;
std::string s = tostr(stats);
//std::cerr << hex_view(s.c_str(), s.c_str() + s.length(), true /*as_text*/) << std::endl;
REQUIRE(s == "<PerGenerationStatistics :used 0 :n_gc 0 :new_alloc_z 0 :scanned_z 0 :survive_z 0 :promote_z 0>");
tag_config::tag_color_enabled = saved;
}
TEST_CASE("GcStatistics", "[alloc][gc]")
{
bool saved = tag_config::tag_color_enabled;
tag_config::tag_color_enabled = false;
GcStatistics stats;
std::string s = tostr(stats);
REQUIRE(s ==
"<GcStatistics"
/**/" :gen_v ["
/***/ "<PerGenerationStatistics"
/****/ " :used 0"
/****/ " :n_gc 0"
/****/ " :new_alloc_z 0"
/****/ " :scanned_z 0"
/****/ " :survive_z 0"
/****/ " :promote_z 0"
/***/ ">"
/***/ " <PerGenerationStatistics"
/****/ " :used 0"
/****/ " :n_gc 0"
/****/ " :new_alloc_z 0"
/****/ " :scanned_z 0"
/****/ " :survive_z 0"
/****/ " :promote_z 0"
/***/ ">]"
/**/ " :total_allocated 0"
/**/ " :total_promoted_sab 0"
">");
tag_config::tag_color_enabled = saved;
}
TEST_CASE("GcStatisticsExt", "[alloc][gc]")
{
bool saved = tag_config::tag_color_enabled;
tag_config::tag_color_enabled = false;
GcStatisticsExt stats({});
std::string s = tostr(stats);
REQUIRE(s == "<GcStatisticsExt :gen_v [<PerGenerationStatistics :used 0 :n_gc 0 :new_alloc_z 0 :scanned_z 0 :survive_z 0 :promote_z 0> <PerGenerationStatistics :used 0 :n_gc 0 :new_alloc_z 0 :scanned_z 0 :survive_z 0 :promote_z 0>] :total_allocated 0 :total_promoted_sab 0 :nursery_z 0 :nursery_before_ckp_z 0 :nursery_after_ckp_z 0 :tenured_z 0 :n_mutation 0 :n_logged_mutation 0 :n_xgen_mutation 0 :n_xckp_mutation 0>");
tag_config::tag_color_enabled = saved;
}
TEST_CASE("GcStatistics-pretty", "[alloc][gc][pretty]")
{
bool saved = tag_config::tag_color_enabled;
tag_config::tag_color_enabled = false;
std::stringstream ss;
ppconfig ppc;
GcStatistics stats;
std::string actual = toppstr2(ppc, stats);
std::string expected
= ("<GcStatistics\n"
" :gen_v\n"
" [ <PerGenerationStatistics\n"
" :used_z 0\n"
" :n_gc 0\n"
" :new_alloc_z 0\n"
" :scanned_z 0\n"
" :survive_z 0\n"
" :promote_z 0>,\n"
" <PerGenerationStatistics\n"
" :used_z 0\n"
" :n_gc 0\n"
" :new_alloc_z 0\n"
" :scanned_z 0\n"
" :survive_z 0\n"
" :promote_z 0> ]\n"
" :total_allocated 0\n"
" :total_promoted_sab 0\n"
" :total_promoted 0\n"
" :n_mutation 0\n"
" :n_logged_mutation 0\n"
" :n_xgen_mutation 0\n"
" :n_xckp_mutation 0>");
if (actual != expected) {
CHECK(actual == expected);
CHECK(actual.length() == expected.length());
auto a_ix = actual.begin();
auto e_ix = expected.begin();
std::size_t pos = 0;
while (a_ix != actual.end() && e_ix != expected.end()) {
INFO(xtag("pos", pos));
INFO(xtag("matching_prefix", std::string(actual.c_str(), pos)));
REQUIRE(*a_ix == *e_ix);
++a_ix;
++e_ix;
++pos;
}
}
REQUIRE(actual == expected);
tag_config::tag_color_enabled = saved;
}
TEST_CASE("GcStatisticsExt-pretty", "[alloc][gc][pretty]")
{
bool saved = tag_config::tag_color_enabled;
tag_config::tag_color_enabled = false;
std::stringstream ss;
ppconfig ppc;
GcStatisticsExt stats({});
std::string actual = toppstr2(ppc, stats);
std::string expected
= ("<GcStatisticsExt\n"
" :gen_v\n"
" [ <PerGenerationStatistics\n"
" :used_z 0\n"
" :n_gc 0\n"
" :new_alloc_z 0\n"
" :scanned_z 0\n"
" :survive_z 0\n"
" :promote_z 0>,\n"
" <PerGenerationStatistics\n"
" :used_z 0\n"
" :n_gc 0\n"
" :new_alloc_z 0\n"
" :scanned_z 0\n"
" :survive_z 0\n"
" :promote_z 0> ]\n"
" :total_allocated 0\n"
" :total_promoted_sab 0\n"
" :total_promoted 0\n"
" :n_mutation 0\n"
" :n_logged_mutation 0\n"
" :n_xgen_mutation 0\n"
" :n_xckp_mutation 0\n"
" :nursery_z 0\n"
" :nursery_before_checkpoint_z 0\n"
" :nursery_after_checkpoint_z 0\n"
" :tenured_z 0>");
if (actual != expected) {
CHECK(actual == expected);
CHECK(actual.length() == expected.length());
auto a_ix = actual.begin();
auto e_ix = expected.begin();
std::size_t pos = 0;
while (a_ix != actual.end() && e_ix != expected.end()) {
INFO(xtag("pos", pos));
INFO(xtag("matching_prefix", std::string(actual.c_str(), pos)));
REQUIRE(*a_ix == *e_ix);
++a_ix;
++e_ix;
++pos;
}
}
REQUIRE(actual == expected);
tag_config::tag_color_enabled = saved;
}
}
} /*namespace xo*/
/* GcStatistics.test.cpp */

58
utest/ListAlloc.test.cpp Normal file
View file

@ -0,0 +1,58 @@
/* ListAlloc.test.cpp
*
* author: Roland Conybeare, Aug 2025
*/
#include "xo/alloc/ListAlloc.hpp"
#include <catch2/catch.hpp>
namespace xo {
using xo::gc::ListAlloc;
namespace ut {
TEST_CASE("ListAlloc", "[alloc][gc]")
{
/** teeny weeny allocator **/
up<ListAlloc> alloc = ListAlloc::make("test", 16, 32, false);
REQUIRE(alloc->name() == "test");
REQUIRE(alloc->size() == 16);
REQUIRE(alloc->before_checkpoint() == 0);
REQUIRE(alloc->after_checkpoint() == 0);
/* will expand */
std::byte * mem1 = alloc->alloc(20);
REQUIRE(mem1);
REQUIRE(alloc->size() == 16 + 32);
/* round up to multiple of 8 */
REQUIRE(alloc->before_checkpoint() == 24);
REQUIRE(alloc->after_checkpoint() == 0);
alloc->checkpoint();
std::byte * mem2 = alloc->alloc(30);
REQUIRE(mem2);
REQUIRE(alloc->size() == 16 + 32 + 48);
REQUIRE(alloc->before_checkpoint() == 24);
/* round up to multiple of 8 */
REQUIRE(alloc->after_checkpoint() == 32);
std::byte * mem3 = alloc->alloc(40);
REQUIRE(mem3);
REQUIRE(alloc->size() == 16 + 32 + 48 + 80);
REQUIRE(alloc->before_checkpoint() == 24);
/* already multiple of 8 */
REQUIRE(alloc->after_checkpoint() == 32 + 40);
REQUIRE(alloc->is_before_checkpoint(mem1) == true);
REQUIRE(alloc->is_before_checkpoint(mem2) == false);
REQUIRE(alloc->is_before_checkpoint(mem3) == false);
}
} /*namespace ut*/
} /*namespace xo*/
/* ListAlloc.test.cpp */

View file

@ -0,0 +1,185 @@
/* @file ObjectStatistics.test.cpp
*
* author: Roland Conybeare, Aug 2025
*/
#include "xo/alloc/ObjectStatistics.hpp"
#include "xo/reflect/Reflect.hpp"
#include "xo/indentlog/scope.hpp"
#include "xo/indentlog/print/ppstr.hpp"
#include "xo/indentlog/print/tostr.hpp"
#include "xo/indentlog/print/hex.hpp"
#include <catch2/catch.hpp>
namespace xo {
using xo::gc::ObjectStatistics;
using xo::gc::PerObjectTypeStatistics;
using xo::reflect::Reflect;
using xo::print::ppconfig;
namespace ut {
TEST_CASE("PerObjectTypeStatistics", "[alloc][gc]")
{
bool saved = tag_config::tag_color_enabled;
tag_config::tag_color_enabled = false;
PerObjectTypeStatistics stats;
std::string s = tostr(stats);
//std::cerr << hex_view(s.c_str(), s.c_str() + s.length(), true /*as_text*/) << std::endl;
REQUIRE(s == "<PerObjectTypeStatistics :td nullptr :scanned_n 0 :scanned_z 0 :survive_n 0 :survive_z 0>");
tag_config::tag_color_enabled = saved;
}
TEST_CASE("PerObjectTypeStatistics-1", "[alloc][gc]")
{
bool saved = tag_config::tag_color_enabled;
tag_config::tag_color_enabled = false;
PerObjectTypeStatistics stats;
stats.td_ = Reflect::require<bool>();
stats.scanned_n_ = 4;
stats.scanned_z_ = 16;
stats.survive_n_ = 2;
stats.survive_z_ = 8;
std::string s = tostr(stats);
//std::cerr << hex_view(s.c_str(), s.c_str() + s.length(), true /*as_text*/) << std::endl;
REQUIRE(s == "<PerObjectTypeStatistics :td bool :scanned_n 4 :scanned_z 16 :survive_n 2 :survive_z 8>");
tag_config::tag_color_enabled = saved;
}
TEST_CASE("ObjectStatistics", "[alloc][gc]")
{
bool saved = tag_config::tag_color_enabled;
tag_config::tag_color_enabled = false;
ObjectStatistics stats;
std::string s = tostr(stats);
REQUIRE(s == "<ObjectStatistics>");
tag_config::tag_color_enabled = saved;
}
TEST_CASE("ObjectStatistics-1", "[alloc][gc]")
{
bool saved = tag_config::tag_color_enabled;
tag_config::tag_color_enabled = false;
ObjectStatistics stats;
stats.per_type_stats_v_.push_back(PerObjectTypeStatistics());
std::string s = tostr(stats);
REQUIRE(s == "<ObjectStatistics :[0] <PerObjectTypeStatistics :td nullptr :scanned_n 0 :scanned_z 0 :survive_n 0 :survive_z 0>>");
tag_config::tag_color_enabled = saved;
}
TEST_CASE("ObjectStatistics-pretty", "[alloc][gc][pretty]")
{
bool saved = tag_config::tag_color_enabled;
tag_config::tag_color_enabled = false;
std::stringstream ss;
ppconfig ppc;
ObjectStatistics stats;
std::string actual = toppstr2(ppc, stats);
std::string expected
= ("<ObjectTypeStatistics :per_type_stats_v []>");
if (actual != expected) {
CHECK(actual == expected);
CHECK(actual.length() == expected.length());
auto a_ix = actual.begin();
auto e_ix = expected.begin();
std::size_t pos = 0;
while (a_ix != actual.end() && e_ix != expected.end()) {
INFO(xtag("pos", pos));
INFO(xtag("matching_prefix", std::string(actual.c_str(), pos)));
REQUIRE(*a_ix == *e_ix);
++a_ix;
++e_ix;
++pos;
}
}
REQUIRE(actual == expected);
tag_config::tag_color_enabled = saved;
}
TEST_CASE("ObjectStatistics-pretty-1", "[alloc][gc][pretty]")
{
bool saved = tag_config::tag_color_enabled;
tag_config::tag_color_enabled = false;
PerObjectTypeStatistics objstats;
objstats.td_ = Reflect::require<bool>();
objstats.scanned_n_ = 4;
objstats.scanned_z_ = 16;
objstats.survive_n_ = 2;
objstats.survive_z_ = 8;
std::stringstream ss;
ppconfig ppc;
ObjectStatistics stats;
stats.per_type_stats_v_.push_back(objstats);
std::string actual = toppstr2(ppc, stats);
std::string expected
= ("<ObjectTypeStatistics\n"
" :per_type_stats_v\n"
" [ <PerObjectTypeStatistics\n"
" :td bool\n"
" :scanned_n 4\n"
" :scanned_z 16\n"
" :survive_n 2\n"
" :survive_z 8> ]>");
if (actual != expected) {
CHECK(actual == expected);
CHECK(actual.length() == expected.length());
auto a_ix = actual.begin();
auto e_ix = expected.begin();
std::size_t pos = 0;
while (a_ix != actual.end() && e_ix != expected.end()) {
INFO(xtag("pos", pos));
INFO(xtag("matching_prefix", std::string(actual.c_str(), pos)));
REQUIRE(*a_ix == *e_ix);
++a_ix;
++e_ix;
++pos;
}
}
tag_config::tag_color_enabled = saved;
}
}
} /*namespace xo*/
/* ObjectStatistics.test.cpp */

38
utest/generation.test.cpp Normal file
View file

@ -0,0 +1,38 @@
/* generation.test.cpp
*
* author: Roland Conybeare, Aug 2025
*/
#include "xo/alloc/generation.hpp"
#include <catch2/catch.hpp>
#include <cstring>
namespace xo {
namespace gc {
TEST_CASE("generation", "[gc]") {
REQUIRE(::strcmp(gen2str(generation::nursery), "nursery") == 0);
REQUIRE(::strcmp(gen2str(generation::tenured), "tenured") == 0);
REQUIRE(::strcmp(gen2str(generation::N), "?generation") == 0);
{
std::stringstream ss;
ss << generation::nursery;
REQUIRE(ss.str() == "nursery");
}
{
std::stringstream ss;
ss << generation::tenured;
REQUIRE(ss.str() == "tenured");
}
{
std::stringstream ss;
ss << generation::N;
REQUIRE(ss.str() == "?generation");
}
}
} /*namespace gc*/
} /*namespace xo*/
/* generation.test.cpp */