diff --git a/README.md b/README.md index 25660760..c1c64ebb 100644 --- a/README.md +++ b/README.md @@ -80,14 +80,21 @@ using `xo-build`. Prepare build ``` # phase 2 -$ cmake -B .build -S . -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=Debug -DCODE_COVERAGE=ON +$ cmake -B .build -S . -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=coverage ``` -Build coverage-enabled libraries and executables +Run coverage-enabled unit tests ``` -$ (cd .build && make ccov) +$ (cd .build && ctest) ``` +Generate coverage report +``` +$ .build/gen-ccov +``` + +Html report left in ``.build/ccov/html/index.html`` + ## To view docs from WSL 1. find wsl IP address diff --git a/xo-alloc/include/xo/alloc/ArenaAlloc.hpp b/xo-alloc/include/xo/alloc/ArenaAlloc.hpp index 51b72c9e..8e162a95 100644 --- a/xo-alloc/include/xo/alloc/ArenaAlloc.hpp +++ b/xo-alloc/include/xo/alloc/ArenaAlloc.hpp @@ -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 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 **/ diff --git a/xo-alloc/include/xo/alloc/GC.hpp b/xo-alloc/include/xo/alloc/GC.hpp index 679e5286..4560f3ac 100644 --- a/xo-alloc/include/xo/alloc/GC.hpp +++ b/xo-alloc/include/xo/alloc/GC.hpp @@ -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 **/ diff --git a/xo-alloc/include/xo/alloc/IAlloc.hpp b/xo-alloc/include/xo/alloc/IAlloc.hpp index 2eacd9cc..b8270f53 100644 --- a/xo-alloc/include/xo/alloc/IAlloc.hpp +++ b/xo-alloc/include/xo/alloc/IAlloc.hpp @@ -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*/ diff --git a/xo-alloc/include/xo/alloc/ListAlloc.hpp b/xo-alloc/include/xo/alloc/ListAlloc.hpp index 627bbf50..ac2f9894 100644 --- a/xo-alloc/include/xo/alloc/ListAlloc.hpp +++ b/xo-alloc/include/xo/alloc/ListAlloc.hpp @@ -29,9 +29,6 @@ namespace xo { ListAlloc(std::unique_ptr 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; }; diff --git a/xo-alloc/include/xo/alloc/generation.hpp b/xo-alloc/include/xo/alloc/generation.hpp index 6a5a7c2e..82c48808 100644 --- a/xo-alloc/include/xo/alloc/generation.hpp +++ b/xo-alloc/include/xo/alloc/generation.hpp @@ -3,6 +3,9 @@ * author: Roland Conybeare, Aug 2025 */ +#pragma once + +#include #include namespace xo { @@ -15,6 +18,13 @@ namespace xo { constexpr std::size_t gen2int(generation x) { return static_cast(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, diff --git a/xo-alloc/src/alloc/ArenaAlloc.cpp b/xo-alloc/src/alloc/ArenaAlloc.cpp index 94053560..e978f566 100644 --- a/xo-alloc/src/alloc/ArenaAlloc.cpp +++ b/xo-alloc/src/alloc/ArenaAlloc.cpp @@ -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::make(const std::string & name, -#ifdef REDLINE_MEMORY - std::size_t rz, -#endif std::size_t z, bool debug_flag) { return up(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(p); - TaggedPtr tp = obj->self_tp(); - std::size_t z = obj->_shallow_size(); + Object * obj = reinterpret_cast(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*/ diff --git a/xo-alloc/src/alloc/CMakeLists.txt b/xo-alloc/src/alloc/CMakeLists.txt index 55b8641c..589f3b90 100644 --- a/xo-alloc/src/alloc/CMakeLists.txt +++ b/xo-alloc/src/alloc/CMakeLists.txt @@ -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}) diff --git a/xo-alloc/src/alloc/Forwarding1.cpp b/xo-alloc/src/alloc/Forwarding1.cpp index 5a941c82..4b47e4f2 100644 --- a/xo-alloc/src/alloc/Forwarding1.cpp +++ b/xo-alloc/src/alloc/Forwarding1.cpp @@ -26,7 +26,7 @@ namespace xo { void Forwarding1::display(std::ostream & os) const { - os << ""; + os << "self_tp().td()->short_name()) << ">"; } Object * diff --git a/xo-alloc/src/alloc/GC.cpp b/xo-alloc/src/alloc/GC.cpp index 460ac770..2990adf6 100644 --- a/xo-alloc/src/alloc/GC.cpp +++ b/xo-alloc/src/alloc/GC.cpp @@ -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); diff --git a/xo-alloc/src/alloc/GcStatistics.cpp b/xo-alloc/src/alloc/GcStatistics.cpp index d54c3cf2..7b7a6ff9 100644 --- a/xo-alloc/src/alloc/GcStatistics.cpp +++ b/xo-alloc/src/alloc/GcStatistics.cpp @@ -32,12 +32,12 @@ namespace xo { PerGenerationStatistics::display(std::ostream & os) const { os << ""; } @@ -62,9 +62,9 @@ namespace xo { GcStatistics::display(std::ostream & os) const { os << ""; } diff --git a/xo-alloc/src/alloc/ListAlloc.cpp b/xo-alloc/src/alloc/ListAlloc.cpp index 1f84d5c6..6e37a325 100644 --- a/xo-alloc/src/alloc/ListAlloc.cpp +++ b/xo-alloc/src/alloc/ListAlloc.cpp @@ -5,6 +5,7 @@ #include "ListAlloc.hpp" #include "ArenaAlloc.hpp" +#include "xo/indentlog/scope.hpp" #include #include @@ -13,9 +14,6 @@ namespace xo { ListAlloc::ListAlloc(std::unique_ptr 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 hd{ArenaAlloc::make(name, -#ifdef REDLINE_MEMORY - 0, -#endif cz, debug_flag)}; if (!hd) @@ -52,9 +44,6 @@ namespace xo { up 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 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*/ diff --git a/xo-alloc/src/alloc/ObjectStatistics.cpp b/xo-alloc/src/alloc/ObjectStatistics.cpp index a69928dc..863a02cb 100644 --- a/xo-alloc/src/alloc/ObjectStatistics.cpp +++ b/xo-alloc/src/alloc/ObjectStatistics.cpp @@ -14,13 +14,13 @@ namespace xo { { os << "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_) << ">"; } diff --git a/xo-alloc/src/alloc/generation.cpp b/xo-alloc/src/alloc/generation.cpp new file mode 100644 index 00000000..54c151ac --- /dev/null +++ b/xo-alloc/src/alloc/generation.cpp @@ -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 */ diff --git a/xo-alloc/utest/ArenaAlloc.test.cpp b/xo-alloc/utest/ArenaAlloc.test.cpp index 0e4582e9..52f135f3 100644 --- a/xo-alloc/utest/ArenaAlloc.test.cpp +++ b/xo-alloc/utest/ArenaAlloc.test.cpp @@ -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()); diff --git a/xo-alloc/utest/CMakeLists.txt b/xo-alloc/utest/CMakeLists.txt index b38a442f..907bafcb 100644 --- a/xo-alloc/utest/CMakeLists.txt +++ b/xo-alloc/utest/CMakeLists.txt @@ -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) diff --git a/xo-alloc/utest/Forwarding1.test.cpp b/xo-alloc/utest/Forwarding1.test.cpp new file mode 100644 index 00000000..e5dfb1fe --- /dev/null +++ b/xo-alloc/utest/Forwarding1.test.cpp @@ -0,0 +1,85 @@ +/* Forwarding1.test.cpp + * + * author: Roland Conybeare, Aug 2025 + */ + +#include "Forwarding1.hpp" +#include "ArenaAlloc.hpp" +#include "xo/reflect/Reflect.hpp" +#include +#include + +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 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(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 data_; + gp member_; + }; + } + + TEST_CASE("Forwarding1", "[gc][alloc]") + { + bool saved = tag_config::tag_color_enabled; + tag_config::tag_color_enabled = false; + + gp obj = new DummyObject("Well, I wasn't expecting that!"); + gp 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() == ""); + + tag_config::tag_color_enabled = saved; + } + + TEST_CASE("IAlloc.assign_member", "[gc][alloc]") + { + /* not giving this nit it's own translation unit. + */ + + gp obj = new DummyObject("This also a surprise.."); + + up 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 */ diff --git a/xo-alloc/utest/GC.test.cpp b/xo-alloc/utest/GC.test.cpp index c36deb2e..d961e3dd 100644 --- a/xo-alloc/utest/GC.test.cpp +++ b/xo-alloc/utest/GC.test.cpp @@ -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_); diff --git a/xo-alloc/utest/GcStatistics.test.cpp b/xo-alloc/utest/GcStatistics.test.cpp new file mode 100644 index 00000000..5f2fbe96 --- /dev/null +++ b/xo-alloc/utest/GcStatistics.test.cpp @@ -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 +#include + +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 == ""); + + 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 == + "" + /***/ " ]" + /**/ " :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 == " ] :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 + = (",\n" + " ]\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 + = (",\n" + " ]\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 */ diff --git a/xo-alloc/utest/ListAlloc.test.cpp b/xo-alloc/utest/ListAlloc.test.cpp new file mode 100644 index 00000000..2db22bf2 --- /dev/null +++ b/xo-alloc/utest/ListAlloc.test.cpp @@ -0,0 +1,58 @@ +/* ListAlloc.test.cpp + * + * author: Roland Conybeare, Aug 2025 + */ + +#include "xo/alloc/ListAlloc.hpp" +#include + +namespace xo { + using xo::gc::ListAlloc; + + namespace ut { + TEST_CASE("ListAlloc", "[alloc][gc]") + { + /** teeny weeny allocator **/ + up 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 */ diff --git a/xo-alloc/utest/ObjectStatistics.test.cpp b/xo-alloc/utest/ObjectStatistics.test.cpp new file mode 100644 index 00000000..7530450d --- /dev/null +++ b/xo-alloc/utest/ObjectStatistics.test.cpp @@ -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 + +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 == ""); + + 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(); + 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 == ""); + + 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 == ""); + + 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 == ">"); + + 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 + = (""); + + 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(); + 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 + = (" ]>"); + + 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 */ diff --git a/xo-alloc/utest/generation.test.cpp b/xo-alloc/utest/generation.test.cpp new file mode 100644 index 00000000..415d6258 --- /dev/null +++ b/xo-alloc/utest/generation.test.cpp @@ -0,0 +1,38 @@ +/* generation.test.cpp + * + * author: Roland Conybeare, Aug 2025 + */ + +#include "xo/alloc/generation.hpp" +#include +#include + +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 */ diff --git a/xo-indentlog/include/xo/indentlog/print/color.hpp b/xo-indentlog/include/xo/indentlog/print/color.hpp index ef91f49f..268b6d9e 100644 --- a/xo-indentlog/include/xo/indentlog/print/color.hpp +++ b/xo-indentlog/include/xo/indentlog/print/color.hpp @@ -144,7 +144,8 @@ namespace xo { class color_impl { public: color_impl(coloring_control_flags flags, color_spec_type spec, Contents && contents) - : flags_{flags}, spec_{spec}, contents_{std::forward(contents)} {} + : flags_{flags}, + spec_{spec}, contents_{std::forward(contents)} {} color_spec_type const & spec() const { return spec_; } std::uint32_t color() const { return spec_.code(); } @@ -162,32 +163,49 @@ namespace xo { } /*print*/ private: - /* controls independently what to print + /** controls independently what to print * \033[38;5;117m hello, world! \033[0m * <------------> <-----------> <-----> * color_on contents color_off - */ + **/ coloring_control_flags flags_ = coloring_control_flags::none; + /** color to use, when @ref color_enabled_ is on **/ color_spec_type spec_; + /** contents to print surrounded by color escapes **/ Contents contents_; }; /*color_impl*/ + template + color_impl with_color_if(bool color_enabled, color_spec_type const & spec, Contents && contents) { + return color_impl((color_enabled + ? coloring_control_flags::all + : coloring_control_flags::contents), + spec, + std::forward(contents)); + } + template color_impl with_color(color_spec_type const & spec, Contents && contents) { - return color_impl(coloring_control_flags::all, spec, std::forward(contents)); + return color_impl(coloring_control_flags::all, + spec, + std::forward(contents)); } inline color_impl color_on(color_spec_type const & spec) { - return color_impl(coloring_control_flags::color_on, spec, 0); + return color_impl(coloring_control_flags::color_on, + spec, + 0); } inline color_impl color_off(color_spec_type const & spec) { /* any spec other than color_spec_type::none() works here */ - return color_impl(coloring_control_flags::color_off, spec, 0); + return color_impl(coloring_control_flags::color_off, + spec, + 0); } template diff --git a/xo-indentlog/include/xo/indentlog/print/pretty.hpp b/xo-indentlog/include/xo/indentlog/print/pretty.hpp index 8068a297..3ae010eb 100644 --- a/xo-indentlog/include/xo/indentlog/print/pretty.hpp +++ b/xo-indentlog/include/xo/indentlog/print/pretty.hpp @@ -20,12 +20,19 @@ namespace xo { /** @class ppstate * @brief hold pretty-printer state * + * Need a log_streambuf instance to keep track of indent. + * Application code will likely prefer @ref pretty_printer + * * Use: + * @code * ppconfig ppc; - * ppstate pps(&cout, 0, &ppc); + * log_streambuf sbuf(buf_z); + * stringstream ss(&sbuf); + * ppstate pps(0, &ppc, &ss, &sbuf); * * pps.pretty("first"); * pps.pretty("second"); + * @endcode **/ struct ppstate { using streambuf_type = log_streambuf>; @@ -176,6 +183,8 @@ namespace xo { /** @class ppstate_standalone * @brief like ppstate, but also holds streambuf + * + * editor bait: pretty_printer prettyprinter */ struct ppstate_standalone : public ppstate { explicit ppstate_standalone(std::ostream * os, std::uint32_t ci, const ppconfig * config) @@ -377,8 +386,9 @@ namespace xo { ppii.pps()->write(" "); /* must color here, because we may keep the output if it fits! */ - if (!ppii.pps()->print_upto(with_color(color_spec_type::yellow(), // tag_config::tag_color, - concat((char const *)":", tag.name())))) + if (!ppii.pps()->print_upto(with_color_if(tag_config::tag_color_enabled, + color_spec_type::yellow(), // tag_config::tag_color, + concat((char const *)":", tag.name())))) return false; ppii.pps()->write(" "); @@ -395,8 +405,9 @@ namespace xo { if (tag.prefix_space()) ppii.pps()->write(" "); - ppii.pps()->write(with_color(color_spec_type::yellow(), //tag_config::tag_color, - concat((char const *)":", tag.name()))); + ppii.pps()->write(with_color_if(tag_config::tag_color_enabled, + color_spec_type::yellow(), //tag_config::tag_color, + concat((char const *)":", tag.name()))); ppii.pps()->newline_indent(ppii.ci1()); ppii.pps()->pretty(tag.value()); @@ -446,6 +457,5 @@ namespace xo { return *this; } - } /*namespace print*/ } /*namespace xo*/ diff --git a/xo-indentlog/include/xo/indentlog/print/tag.hpp b/xo-indentlog/include/xo/indentlog/print/tag.hpp index 678eb0f7..026e8205 100644 --- a/xo-indentlog/include/xo/indentlog/print/tag.hpp +++ b/xo-indentlog/include/xo/indentlog/print/tag.hpp @@ -181,7 +181,7 @@ namespace xo { if (PrefixSpace) s << " "; - s << with_color(tag_config::tag_color, concat((char const *)":", tag.name())) + s << with_color_if(tag_config::tag_color_enabled, tag_config::tag_color, concat((char const *)":", tag.name())) << " "; if (TagStyle == tagstyle::autoescape) @@ -204,7 +204,7 @@ namespace xo { if (PrefixSpace) s << " "; - s << with_color(tag_config::tag_color, concat((char const *)":", tag.name())) + s << with_color_if(tag_config::tag_color_enabled, tag_config::tag_color, concat((char const *)":", tag.name())) << " "; if (TagStyle == tagstyle::autoescape) diff --git a/xo-indentlog/include/xo/indentlog/print/tag_config.hpp b/xo-indentlog/include/xo/indentlog/print/tag_config.hpp index f86b2c8f..e9c76cd5 100644 --- a/xo-indentlog/include/xo/indentlog/print/tag_config.hpp +++ b/xo-indentlog/include/xo/indentlog/print/tag_config.hpp @@ -6,18 +6,29 @@ #include namespace xo { - /* Tag here b/c we want header-only library */ + /** @class tag_config_impl + * @brief configuration for tag-printing + * + * note: Tag here b/c we want header-only library + **/ template struct tag_config_impl { - /* color to use for tags + /** true to enable colored tag printing **/ + static bool tag_color_enabled; + /** color to use for tags * os << tag("foo", foovalue) * to produces output like * :foo foovalue * with :foo using .tag_color - */ + **/ static color_spec_type tag_color; }; /*tag_config_impl*/ + template + bool + tag_config_impl::tag_color_enabled = true; + + /** default value is a light gray **/ template color_spec_type tag_config_impl::tag_color = color_spec_type::xterm(245); diff --git a/xo-object/src/object/List.cpp b/xo-object/src/object/List.cpp index 324605d5..ae037915 100644 --- a/xo-object/src/object/List.cpp +++ b/xo-object/src/object/List.cpp @@ -80,9 +80,13 @@ namespace xo { gp l = const_cast(this); os << "("; + size_t i = 0; while (!l->is_nil()) { + if (i > 0) + os << " "; os << l->head(); l = l->rest(); + ++i; } os << ")"; } diff --git a/xo-object/src/object/String.cpp b/xo-object/src/object/String.cpp index 41399a2e..d5e00928 100644 --- a/xo-object/src/object/String.cpp +++ b/xo-object/src/object/String.cpp @@ -7,6 +7,7 @@ #include "GC.hpp" #include "TaggedPtr.hpp" #include "xo/reflect/Reflect.hpp" +#include "xo/indentlog/print/quoted.hpp" #include #include #include @@ -15,6 +16,7 @@ namespace xo { using xo::reflect::Reflect; using xo::reflect::TaggedPtr; + using xo::print::quot; namespace obj { String::String(owner owner, std::size_t z, char * s) @@ -98,9 +100,7 @@ namespace xo { void String::display(std::ostream & os) const { - // TODO: print with escapes - - os << "\"" << c_str() << "\""; + os << quot(c_str()); } // ----- GC support ----- diff --git a/xo-object/utest/Boolean.test.cpp b/xo-object/utest/Boolean.test.cpp index 601de00e..45152ae1 100644 --- a/xo-object/utest/Boolean.test.cpp +++ b/xo-object/utest/Boolean.test.cpp @@ -41,6 +41,21 @@ namespace xo { // booleans are global constants REQUIRE(gc->tospace_generation_of(btrue.ptr()) == generation_result::not_found); REQUIRE(gc->tospace_generation_of(bfalse.ptr()) == generation_result::not_found); + + REQUIRE(btrue->self_tp().td()->short_name() == "Boolean"); + REQUIRE(bfalse->self_tp().td()->short_name() == "Boolean"); + + { + std::stringstream ss; + ss << btrue; + REQUIRE(ss.str() == "#t"); + } + + { + std::stringstream ss; + ss << bfalse; + REQUIRE(ss.str() == "#f"); + } } } /*namespace ut*/ } /*namespace xo*/ diff --git a/xo-object/utest/CMakeLists.txt b/xo-object/utest/CMakeLists.txt index 42ed1c57..e98af8ad 100644 --- a/xo-object/utest/CMakeLists.txt +++ b/xo-object/utest/CMakeLists.txt @@ -4,6 +4,7 @@ set(UTEST_EXE utest.object) set(UTEST_SRCS object_utest_main.cpp Boolean.test.cpp + Integer.test.cpp String.test.cpp List.test.cpp GC.test.cpp) diff --git a/xo-object/utest/GC.test.cpp b/xo-object/utest/GC.test.cpp index 481dbecf..b139f4a4 100644 --- a/xo-object/utest/GC.test.cpp +++ b/xo-object/utest/GC.test.cpp @@ -424,21 +424,23 @@ namespace xo { void RandomMutationModel::generate_random_roots(GC * gc, xoshiro256ss * p_rgen) { - std::size_t w1_ix = (*p_rgen)() % n_; - { - root_v_.push_back(w2_.at(w1_ix)); - for (std::size_t i = 1; i < r_; ++i) { - std::size_t w2_ix = (*p_rgen)() % (m_ + n_); + if (n_ > 0) { + std::size_t w1_ix = (*p_rgen)() % n_; - root_v_.push_back(w2_.at(w2_ix)); - } - - REQUIRE(root_v_.size() == r_); - - for (auto & root : root_v_) - gc->add_gc_root(root.ptr_address()); + if (r_ > 0) + root_v_.push_back(w2_.at(w1_ix)); } + for (std::size_t i = 1; i < r_; ++i) { + std::size_t w2_ix = (*p_rgen)() % (m_ + n_); + + root_v_.push_back(w2_.at(w2_ix)); + } + + REQUIRE(root_v_.size() == r_); + + for (auto & root : root_v_) + gc->add_gc_root(root.ptr_address()); } void RandomMutationModel::generate_random_mutations(xoshiro256ss * p_rgen) @@ -547,6 +549,8 @@ namespace xo { std::vector s_testcase_v = { + // segfault with + /* nz: nursery size * tz: tenured size * m: #of random list cells to create @@ -556,7 +560,10 @@ namespace xo { * k: #of random mutations to apply * * nz tz m n r rr k stats, debug */ - testcase_stresstest(1024, 1024, 5, 1, 5, 2, 10, true, false), + testcase_stresstest( 16, 1024, 2, 0, 0, 0, 0, false, false), + testcase_stresstest( 32, 1024, 2, 1, 5, 0, 0, false, false), + testcase_stresstest( 64, 1024, 5, 2, 5, 2, 10, false, false), + // testcase_stresstest( 128, 1024, 5, 2, 5, 2, 10, true, true), // segfault bad list cell testcase_stresstest(1024, 1024, 10, 10, 5, 2, 10, true, false) }; } /*namespace*/ @@ -600,12 +607,12 @@ namespace xo { auto rgen = xoshiro256ss(seed); REQUIRE(tc.m_ > 0); - REQUIRE(tc.n_ > 0); - REQUIRE(tc.r_ > 0); + //REQUIRE(tc.n_ > 0); + //REQUIRE(tc.r_ > 0); RandomMutationModel data_model(tc.m_, tc.n_, tc.r_, tc.rr_, tc.k_); - for (std::size_t cycle = 0; cycle < 5; ++cycle) { + for (std::size_t cycle = 0; cycle < 3; ++cycle) { INFO(xtag("cycle", cycle)); if (cycle == 0) { @@ -635,7 +642,7 @@ namespace xo { gc->request_gc(generation::nursery); /* collector cycle changed object addresses. - * build a new object model, and verify consistency with from_model + * build a new object model, and verify consiste1ncy with from_model */ ObjectGraphModel to_model; to_model.from_root_vector(data_model.root_v_); diff --git a/xo-object/utest/Integer.test.cpp b/xo-object/utest/Integer.test.cpp new file mode 100644 index 00000000..94a4e4e2 --- /dev/null +++ b/xo-object/utest/Integer.test.cpp @@ -0,0 +1,58 @@ +/* @file Integer.test.cpp + * + * author Roland Conybeare, Aug 2025 + */ + +#include "xo/object/Integer.hpp" +#include "xo/alloc/GC.hpp" +#include + +namespace xo { + using xo::gc::GC; + using xo::gc::generation_result; + using xo::obj::Integer; + + namespace ut { + TEST_CASE("Integer", "[Integer]") + { + up gc = GC::make( + { .initial_nursery_z_ = 1024, + .initial_tenured_z_ = 1024 + }); + + REQUIRE(gc.get()); + + Object::mm = gc.get(); + + gp i1 = Integer::make(123); + gp i2 = Integer::make(-321); + + REQUIRE(i1->value() == 123); + REQUIRE(i2->value() == -321); + + REQUIRE(i1->_shallow_size() == sizeof(Integer)); + REQUIRE(i2->_shallow_size() == sizeof(Integer)); + + REQUIRE(gc->tospace_generation_of(i1.ptr()) == generation_result::nursery); + REQUIRE(gc->tospace_generation_of(i2.ptr()) == generation_result::nursery); + + REQUIRE(i1->self_tp().td()->short_name() == "Integer"); + REQUIRE(i2->self_tp().td()->short_name() == "Integer"); + + { + std::stringstream ss; + ss << i1; + REQUIRE(ss.str() == "123"); + } + + { + std::stringstream ss; + ss << i2; + REQUIRE(ss.str() == "-321"); + } + } + + } /*namespace ut*/ +} /*namespace xo*/ + +/* Integer.test.cpp */ diff --git a/xo-object/utest/List.test.cpp b/xo-object/utest/List.test.cpp index 66409be7..925c7a7c 100644 --- a/xo-object/utest/List.test.cpp +++ b/xo-object/utest/List.test.cpp @@ -5,7 +5,9 @@ #include "xo/object/List.hpp" #include "xo/object/String.hpp" +#include "xo/object/Integer.hpp" #include "xo/alloc/GC.hpp" +#include "xo/alloc/ArenaAlloc.hpp" #include "xo/indentlog/scope.hpp" #include #include @@ -16,21 +18,25 @@ namespace xo { namespace ut { using xo::obj::List; using xo::obj::String; + using xo::obj::Integer; using xo::gc::GC; using xo::gc::generation_result; using xo::gc::generation; + using xo::gc::ArenaAlloc; namespace { struct Testcase_List { Testcase_List(std::size_t nz, std::size_t tz, const std::vector> & v) - : nursery_z_{nz}, tenured_z_{tz}, v_{v} + : nursery_z_{nz}, tenured_z_{tz}, v_{v} {} std::size_t nursery_z_; std::size_t tenured_z_; std::vector> v_; + + std::string expect_display_; }; std::vector @@ -277,6 +283,24 @@ namespace xo { } } + TEST_CASE("List.display", "[List]") + { + constexpr bool c_debug_flag = false; + + up alloc = ArenaAlloc::make("arena", 1024, c_debug_flag); + + Object::mm = alloc.get(); + + gp l = List::list(Integer::make(1), Integer::make(2), Integer::make(3)); + + REQUIRE(l->size() == 3); + + std::stringstream ss; + ss << l; + + REQUIRE(ss.str() == "(1 2 3)"); + } + } /*namespace ut*/ } /*namespace xo*/ diff --git a/xo-object/utest/String.test.cpp b/xo-object/utest/String.test.cpp index 68213858..c3ea4ff4 100644 --- a/xo-object/utest/String.test.cpp +++ b/xo-object/utest/String.test.cpp @@ -157,9 +157,6 @@ namespace xo { { const bool c_debug_flag = false; up arena = ArenaAlloc::make("testarena", -#ifdef REDLINE_MEMORY - 0, -#endif 16*1024, c_debug_flag); Object::mm = arena.get(); @@ -174,8 +171,22 @@ namespace xo { REQUIRE(s3.ptr()); REQUIRE(s3->length() == s1->length() + s2->length()); REQUIRE(::strcmp(s1->c_str(), "the quick")); + + { + std::stringstream ss1; + ss1 << s1; + REQUIRE(ss1.str() == "\"the\""); + } + + /* on printing, escape embedded " chars */ + { + std::stringstream ss4; + ss4 << String::share("\"Allo!\", he said"); + REQUIRE(ss4.str() == "\"\\\"Allo!\\\", he said\""); + } } + } /*namespace ut*/ } /*namespace xo*/