xo-alloc2 : work on X1Collector unit test [WIP]
This commit is contained in:
parent
0c2cd7a64e
commit
1fd5d544f2
14 changed files with 542 additions and 41 deletions
|
|
@ -10,6 +10,17 @@
|
|||
namespace xo {
|
||||
namespace mm {
|
||||
|
||||
/** **/
|
||||
struct AllocHeader {
|
||||
using repr_type = std::uint64_t;
|
||||
|
||||
explicit AllocHeader(repr_type x) : repr_{x} {}
|
||||
|
||||
repr_type repr_;
|
||||
};
|
||||
|
||||
static_assert(sizeof(AllocHeader) == sizeof(AllocHeader::repr_type));
|
||||
|
||||
/** @class DArena
|
||||
*
|
||||
* @brief represent arena allocator state
|
||||
|
|
@ -35,8 +46,10 @@ namespace xo {
|
|||
|
||||
/** @brief an amount of memory **/
|
||||
using size_type = std::size_t;
|
||||
/** @brief allocation pointer; use for allocation results **/
|
||||
using value_type = std::byte*;
|
||||
/** @brief a contiguous memory range **/
|
||||
using range_type = std::pair<std::byte*,std::byte*>;
|
||||
using range_type = std::pair<value_type, value_type>;
|
||||
/** @brief type for allocation header (if enabled) **/
|
||||
using header_type = std::uint64_t;
|
||||
|
||||
|
|
@ -51,7 +64,7 @@ namespace xo {
|
|||
/** null ctor **/
|
||||
DArena() = default;
|
||||
/** ctor from already-mapped (but not committed) address range **/
|
||||
DArena(const ArenaConfig & cfg, size_type page_z, std::byte * lo, std::byte * hi);
|
||||
DArena(const ArenaConfig & cfg, size_type page_z, value_type lo, value_type hi);
|
||||
/** DArena is not copyable **/
|
||||
DArena(const DArena & other) = delete;
|
||||
/** move ctor **/
|
||||
|
|
@ -67,12 +80,12 @@ namespace xo {
|
|||
/** @defgroup mm-arena-methods **/
|
||||
///@{
|
||||
|
||||
size_type reserved() const { return hi_ - lo_; }
|
||||
size_type allocated() const { return free_ - lo_; }
|
||||
size_type committed() const { return committed_z_; }
|
||||
size_type available() const { return limit_ - free_; }
|
||||
size_type reserved() const noexcept { return hi_ - lo_; }
|
||||
size_type allocated() const noexcept { return free_ - lo_; }
|
||||
size_type committed() const noexcept { return committed_z_; }
|
||||
size_type available() const noexcept { return limit_ - free_; }
|
||||
|
||||
bool contains(void * addr) const { return (lo_ <= addr) && (addr < hi_); }
|
||||
bool contains(const void * addr) const noexcept { return (lo_ <= addr) && (addr < hi_); }
|
||||
|
||||
/** obtain uncommitted contiguous memory range comprising
|
||||
* a whole multiple of @p hugepage_z bytes, of at least size @p req_z,
|
||||
|
|
@ -81,10 +94,17 @@ namespace xo {
|
|||
static range_type map_aligned_range(size_type req_z, size_type hugepage_z);
|
||||
|
||||
/** true if arena is mapped i.e. has a reserved address range **/
|
||||
bool is_mapped() const { return (lo_ != nullptr) && (hi_ != nullptr); }
|
||||
bool is_mapped() const noexcept { return (lo_ != nullptr) && (hi_ != nullptr); }
|
||||
|
||||
/** get header from allocated object address **/
|
||||
header_type * obj2hdr(void * obj);
|
||||
header_type * obj2hdr(void * obj) noexcept;
|
||||
|
||||
/** discard all allocated memory, return to empty state
|
||||
* Promise:
|
||||
* - committed memory unchanged
|
||||
* - available memory = committed memory
|
||||
**/
|
||||
void clear() noexcept;
|
||||
|
||||
///@}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,12 @@ namespace xo {
|
|||
constexpr std::uint64_t tseq_mask_unshifted() const;
|
||||
constexpr std::uint64_t tseq_mask_shifted() const;
|
||||
|
||||
public:
|
||||
// ----- Instance Variables -----
|
||||
|
||||
/** optional name, for diagnostics **/
|
||||
std::string name_;
|
||||
|
||||
/** Configuration for collector spaces.
|
||||
* Will have at least {nursery,tenured} x {from,to} spaces.
|
||||
* Not using name_ member.
|
||||
|
|
@ -135,32 +141,75 @@ namespace xo {
|
|||
// ----- DX1Collector -----
|
||||
|
||||
struct DX1Collector {
|
||||
using size_type = DArena::size_type;
|
||||
using value_type = DArena::value_type;
|
||||
using header_type = DArena::header_type;
|
||||
|
||||
explicit DX1Collector(const CollectorConfig & cfg);
|
||||
|
||||
const DArena * get_space(role r, generation g) const { return space_[r][g]; }
|
||||
DArena * get_space(role r, generation g) { return space_[r][g]; }
|
||||
DArena * from_space(generation g) { return get_space(role::from_space(), g); }
|
||||
DArena * to_space(generation g) { return get_space(role::to_space(), g); }
|
||||
const DArena * get_space(role r, generation g) const noexcept { return space_[r][g]; }
|
||||
DArena * get_space(role r, generation g) noexcept { return space_[r][g]; }
|
||||
DArena * from_space(generation g) noexcept { return get_space(role::from_space(), g); }
|
||||
DArena * to_space(generation g) noexcept { return get_space(role::to_space(), g); }
|
||||
DArena * new_space() noexcept { return to_space(generation{0}); }
|
||||
|
||||
/** total reserved memory in bytes, across all {role, generation} **/
|
||||
size_type reserved_total() const noexcept;
|
||||
/** total size in bytes (same as committed_total()) **/
|
||||
size_type size_total() const noexcept;
|
||||
/** total committed memory in bytes, across all {role, generation} **/
|
||||
size_type committed_total() const noexcept;
|
||||
/** total available memory in bytes, across all {role, generation} **/
|
||||
size_type available_total() const noexcept;
|
||||
/** total allocated memory in bytes, across all {role, generation} **/
|
||||
size_type allocated_total() const noexcept;
|
||||
|
||||
/** true iff address @p addr allocated from this collector
|
||||
* in role @p r (according to current GC state)
|
||||
**/
|
||||
bool contains(role r, void * addr) const;
|
||||
bool contains(role r, const void * addr) const noexcept;
|
||||
|
||||
/** return details from last error (will be in gen0 to-space) **/
|
||||
AllocatorError last_error() const noexcept;
|
||||
|
||||
/** get allocation size from header **/
|
||||
std::size_t header2size(header_type hdr) const;
|
||||
std::size_t header2size(header_type hdr) const noexcept;
|
||||
/** get generation counter from alloc header **/
|
||||
generation header2gen(header_type hdr) const;
|
||||
generation header2gen(header_type hdr) const noexcept;
|
||||
/** get tseq from alloc header **/
|
||||
uint32_t header2tseq(header_type hdr) const;
|
||||
uint32_t header2tseq(header_type hdr) const noexcept;
|
||||
|
||||
/** true iff original alloc has been replaced by a forwarding pointer **/
|
||||
bool is_forwarding_header(header_type hdr) const;
|
||||
bool is_forwarding_header(header_type hdr) const noexcept;
|
||||
|
||||
// ----- allocation -----
|
||||
|
||||
/** simple allocation. new allocs always in gen0 to-space **/
|
||||
value_type alloc(size_type z) noexcept;
|
||||
/** compound allocation. To be followed immediately by:
|
||||
* 1. zero or more calls to sub_alloc(zi, complete=false), then
|
||||
* 2. exactly one call to sub_alloc(zi, complete=true)
|
||||
* all the allocs in a compound allocation share the same
|
||||
* allocation header. End state is equivalent to a single
|
||||
* allocation with size z + sum(zi).
|
||||
* New allocs always in gen0 to-space
|
||||
**/
|
||||
value_type super_alloc(size_type z) noexcept;
|
||||
/** sub-allocation with preceding compound allocation.
|
||||
* New allocs always in gen0 to-space
|
||||
**/
|
||||
value_type sub_alloc(size_type z, bool complete) noexcept;
|
||||
/** expand gen0 committed size to at least @p z.
|
||||
**/
|
||||
bool expand(size_type z) noexcept;
|
||||
|
||||
// ----- book-keeping -----
|
||||
|
||||
/** reverse to-space and from-space roles for generation g **/
|
||||
void reverse_roles(generation g);
|
||||
void reverse_roles(generation g) noexcept;
|
||||
|
||||
/** discard all allocated memory **/
|
||||
void clear() noexcept;
|
||||
|
||||
public:
|
||||
/** garbage collector configuration **/
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "alloc2/alloc/Allocator.hpp"
|
||||
#include "alloc2/alloc/IAllocator_Xfer.hpp"
|
||||
#include "Allocator.hpp"
|
||||
#include "DX1Collector.hpp"
|
||||
|
||||
namespace xo {
|
||||
|
|
@ -38,6 +37,7 @@ namespace xo {
|
|||
static std::string_view name(const DX1Collector &) noexcept;
|
||||
/** reserved memory across all {roles, generations} **/
|
||||
static size_type reserved(const DX1Collector &) noexcept;
|
||||
/** 'size'. synonym for committed size **/
|
||||
static size_type size(const DX1Collector &) noexcept;
|
||||
/** committed size accross all {roles, generations} **/
|
||||
static size_type committed(const DX1Collector &) noexcept;
|
||||
|
|
@ -50,14 +50,13 @@ namespace xo {
|
|||
/** report last error, if any, for collector @p d **/
|
||||
static AllocatorError last_error(const DX1Collector &) noexcept;
|
||||
|
||||
/** always alloc in gen0 to-space **/
|
||||
static value_type alloc(DX1Collector & d, size_type z) noexcept;
|
||||
static value_type super_alloc(DX1Collector & d, size_type z) noexcept;
|
||||
static value_type sub_alloc(DX1Collector & d, size_type z, bool complete) noexcept;
|
||||
/** expand gen0 spaces (both from-space and to-space) **/
|
||||
static bool expand(DX1Collector & d, size_type z) noexcept;
|
||||
|
||||
/** always alloc in gen0 to-space **//
|
||||
static value_type alloc(DX1Collector & d, size_type z);
|
||||
static value_type super_alloc(DX1Collector & d, size_type z);
|
||||
static value_type sub_alloc(DX1Collector & d, size_type z, bool complete);
|
||||
|
||||
/** reset to empty state; clears all generations **/
|
||||
static void clear(DX1Collector & d);
|
||||
/** invoke destructor **/
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ namespace xo {
|
|||
|
||||
explicit constexpr generation(value_type x) : value_{x} {}
|
||||
|
||||
static generation nursery() { return generation{0}; }
|
||||
|
||||
constexpr operator value_type() const { return value_; }
|
||||
|
||||
generation & operator++() { ++value_; return *this; }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
|
|
@ -19,6 +20,11 @@ namespace xo {
|
|||
static constexpr role to_space() { return role{0}; }
|
||||
static constexpr role from_space() { return role{1}; }
|
||||
|
||||
static constexpr std::array<role, c_n_role> all() { return {{to_space(), from_space()}}; }
|
||||
|
||||
static constexpr role begin() { return role{0}; }
|
||||
static constexpr role end() { return role{2}; }
|
||||
|
||||
operator value_type() const { return role_; }
|
||||
|
||||
value_type role_ = 0;
|
||||
|
|
|
|||
|
|
@ -240,14 +240,18 @@ namespace xo {
|
|||
}
|
||||
|
||||
DArena::header_type *
|
||||
DArena::obj2hdr(void * obj)
|
||||
DArena::obj2hdr(void * obj) noexcept
|
||||
{
|
||||
assert(config_.store_header_flag_);
|
||||
|
||||
return (header_type *)((byte *)obj - sizeof(header_type));
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
DArena::clear() noexcept
|
||||
{
|
||||
this->free_ = lo_;
|
||||
}
|
||||
}
|
||||
} /*namespace xo*/
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,18 @@
|
|||
* @author Roland Conybeare, Dec 2025
|
||||
**/
|
||||
|
||||
#include "Allocator.hpp"
|
||||
#include "arena/IAllocator_DArena.hpp"
|
||||
#include "gc/DX1Collector.hpp"
|
||||
#include "gc/generation.hpp"
|
||||
#include <_types/_uint32_t.h>
|
||||
#include <xo/facet/obj.hpp>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
using xo::mm::AAllocator;
|
||||
using xo::facet::with_facet;
|
||||
|
||||
namespace mm {
|
||||
#ifdef NOT_USING
|
||||
constexpr std::uint64_t
|
||||
|
|
@ -75,6 +80,8 @@ namespace xo {
|
|||
|
||||
// ----- DX1Collector -----
|
||||
|
||||
using size_type = xo::mm::DX1Collector::size_type;
|
||||
|
||||
DX1Collector::DX1Collector(const CollectorConfig & cfg) : config_{cfg}
|
||||
{
|
||||
assert(config_.arena_config_.header_size_bits_ + config_.gen_bits_ + config_.tseq_bits_ <= 64);
|
||||
|
|
@ -94,18 +101,81 @@ namespace xo {
|
|||
}
|
||||
|
||||
bool
|
||||
DX1Collector::contains(role r, void * addr) const
|
||||
DX1Collector::contains(role r, const void * addr) const noexcept
|
||||
{
|
||||
for (generation gi{0}; gi < config_.n_generation_; ++gi) {
|
||||
if (get_space(r, gi)->contains(addr))
|
||||
const DArena * arena = get_space(r, gi);
|
||||
|
||||
if (arena->contains(addr))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
DX1Collector::header2size(header_type hdr) const
|
||||
AllocatorError
|
||||
DX1Collector::last_error() const noexcept
|
||||
{
|
||||
// TODO:
|
||||
// need to adjust here if runtime errors
|
||||
// encountered during gc.
|
||||
|
||||
return get_space(role::to_space(), generation::nursery())->last_error_;
|
||||
}
|
||||
|
||||
namespace {
|
||||
size_type
|
||||
accumulate_total_aux(const DX1Collector & d,
|
||||
size_t (DArena::* get_stat_fn)() const) noexcept
|
||||
{
|
||||
size_t z = 0;
|
||||
|
||||
for (role ri : role::all()) {
|
||||
for (generation gj{0}; gj < d.config_.n_generation_; ++gj) {
|
||||
const DArena * arena = d.get_space(ri, gj);
|
||||
|
||||
assert(arena);
|
||||
|
||||
z += (arena->*get_stat_fn)();
|
||||
}
|
||||
}
|
||||
|
||||
return z;
|
||||
}
|
||||
}
|
||||
|
||||
size_type
|
||||
DX1Collector::reserved_total() const noexcept
|
||||
{
|
||||
return accumulate_total_aux(*this, &DArena::reserved);
|
||||
}
|
||||
|
||||
size_type
|
||||
DX1Collector::size_total() const noexcept
|
||||
{
|
||||
return committed_total();
|
||||
}
|
||||
|
||||
size_type
|
||||
DX1Collector::committed_total() const noexcept
|
||||
{
|
||||
return accumulate_total_aux(*this, &DArena::committed);
|
||||
}
|
||||
|
||||
size_type
|
||||
DX1Collector::available_total() const noexcept
|
||||
{
|
||||
return accumulate_total_aux(*this, &DArena::available);
|
||||
}
|
||||
|
||||
size_type
|
||||
DX1Collector::allocated_total() const noexcept
|
||||
{
|
||||
return accumulate_total_aux(*this, &DArena::allocated);
|
||||
}
|
||||
|
||||
size_type
|
||||
DX1Collector::header2size(header_type hdr) const noexcept
|
||||
{
|
||||
uint32_t z = (hdr & config_.arena_config_.header_size_mask_);
|
||||
|
||||
|
|
@ -113,7 +183,7 @@ namespace xo {
|
|||
}
|
||||
|
||||
generation
|
||||
DX1Collector::header2gen(header_type hdr) const
|
||||
DX1Collector::header2gen(header_type hdr) const noexcept
|
||||
{
|
||||
uint32_t g = (hdr & config_.gen_mask_shifted()) >> config_.gen_shift();
|
||||
|
||||
|
|
@ -123,7 +193,7 @@ namespace xo {
|
|||
}
|
||||
|
||||
uint32_t
|
||||
DX1Collector::header2tseq(header_type hdr) const
|
||||
DX1Collector::header2tseq(header_type hdr) const noexcept
|
||||
{
|
||||
uint32_t tseq = (hdr & config_.tseq_mask_shifted()) >> config_.tseq_shift();
|
||||
|
||||
|
|
@ -131,18 +201,56 @@ namespace xo {
|
|||
}
|
||||
|
||||
bool
|
||||
DX1Collector::is_forwarding_header(header_type hdr) const
|
||||
DX1Collector::is_forwarding_header(header_type hdr) const noexcept
|
||||
{
|
||||
/** all 1 bits to flag forwarding pointer **/
|
||||
return header2tseq(hdr) == config_.tseq_mask_shifted();
|
||||
}
|
||||
|
||||
auto
|
||||
DX1Collector::alloc(size_type z) noexcept -> value_type
|
||||
{
|
||||
return with_facet<AAllocator>::mkobj(new_space()).alloc(z);
|
||||
}
|
||||
|
||||
auto
|
||||
DX1Collector::super_alloc(size_type z) noexcept -> value_type {
|
||||
return with_facet<AAllocator>::mkobj(new_space()).super_alloc(z);
|
||||
}
|
||||
|
||||
auto
|
||||
DX1Collector::sub_alloc(size_type z, bool complete) noexcept -> value_type {
|
||||
return with_facet<AAllocator>::mkobj(new_space()).sub_alloc(z, complete);
|
||||
}
|
||||
|
||||
bool
|
||||
DX1Collector::expand(size_type z) noexcept
|
||||
{
|
||||
if (with_facet<AAllocator>::mkobj(to_space(generation{0})).expand(z))
|
||||
return with_facet<AAllocator>::mkobj(from_space(generation{0})).expand(z);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
DX1Collector::reverse_roles(generation g) {
|
||||
DX1Collector::reverse_roles(generation g) noexcept {
|
||||
assert(g < config_.n_generation_);
|
||||
|
||||
std::swap(space_[0][g], space_[1][g]);
|
||||
}
|
||||
|
||||
void
|
||||
DX1Collector::clear() noexcept {
|
||||
for (role ri : role::all()) {
|
||||
for (generation gj{0}; gj < config_.n_generation_; ++gj) {
|
||||
DArena * arena = this->get_space(ri, gj);
|
||||
|
||||
assert(arena);
|
||||
|
||||
arena->clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
} /*namespace mm*/
|
||||
} /*namespace xo*/
|
||||
|
||||
|
|
|
|||
|
|
@ -393,7 +393,7 @@ namespace xo {
|
|||
void
|
||||
IAllocator_DArena::clear(DArena & s)
|
||||
{
|
||||
s.free_ = s.lo_;
|
||||
s.clear();
|
||||
//s.checkpoint_ = s.lo_;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,102 @@
|
|||
/** @file IAllocator_DX1Collector.cpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
*
|
||||
* See also ICollector_DX1Collector.cpp for collector facet
|
||||
**/
|
||||
|
||||
#include "gc/IAllocator_DX1Collector.hpp"
|
||||
|
||||
namespace xo {
|
||||
using std::size_t;
|
||||
|
||||
namespace mm {
|
||||
using value_type = IAllocator_DX1Collector::value_type;
|
||||
|
||||
std::string_view
|
||||
IAllocator_DX1Collector::name(const DX1Collector & d) noexcept
|
||||
{
|
||||
return d.config_.name_;
|
||||
}
|
||||
|
||||
auto
|
||||
IAllocator_DX1Collector::reserved(const DX1Collector & d) noexcept -> size_type
|
||||
{
|
||||
return d.reserved_total();
|
||||
}
|
||||
|
||||
auto
|
||||
IAllocator_DX1Collector::size(const DX1Collector & d) noexcept -> size_type
|
||||
{
|
||||
return d.size_total();
|
||||
}
|
||||
|
||||
auto
|
||||
IAllocator_DX1Collector::committed(const DX1Collector & d) noexcept -> size_type
|
||||
{
|
||||
return d.committed_total();
|
||||
}
|
||||
|
||||
auto
|
||||
IAllocator_DX1Collector::available(const DX1Collector & d) noexcept -> size_type
|
||||
{
|
||||
return d.available_total();
|
||||
}
|
||||
|
||||
auto
|
||||
IAllocator_DX1Collector::allocated(const DX1Collector & d) noexcept -> size_type
|
||||
{
|
||||
return d.allocated_total();
|
||||
}
|
||||
|
||||
bool
|
||||
IAllocator_DX1Collector::contains(const DX1Collector & d, const void * addr) noexcept
|
||||
{
|
||||
return d.contains(role::to_space(), addr);
|
||||
}
|
||||
|
||||
AllocatorError
|
||||
IAllocator_DX1Collector::last_error(const DX1Collector & d) noexcept
|
||||
{
|
||||
return d.last_error();
|
||||
}
|
||||
|
||||
auto
|
||||
IAllocator_DX1Collector::alloc(DX1Collector & d, size_type z) noexcept -> value_type
|
||||
{
|
||||
return d.alloc(z);
|
||||
}
|
||||
|
||||
auto
|
||||
IAllocator_DX1Collector::super_alloc(DX1Collector & d, size_type z) noexcept -> value_type
|
||||
{
|
||||
return d.super_alloc(z);
|
||||
}
|
||||
|
||||
auto
|
||||
IAllocator_DX1Collector::sub_alloc(DX1Collector & d, size_type z, bool complete) noexcept -> value_type
|
||||
{
|
||||
return d.sub_alloc(z, complete);
|
||||
}
|
||||
|
||||
bool
|
||||
IAllocator_DX1Collector::expand(DX1Collector & d, size_type z) noexcept
|
||||
{
|
||||
return d.expand(z);
|
||||
}
|
||||
|
||||
void
|
||||
IAllocator_DX1Collector::clear(DX1Collector & d)
|
||||
{
|
||||
d.clear();
|
||||
}
|
||||
|
||||
void
|
||||
IAllocator_DX1Collector::destruct_data(DX1Collector & d)
|
||||
{
|
||||
d.~DX1Collector();
|
||||
}
|
||||
|
||||
} /*namespace mm*/
|
||||
} /*namespace xo*/
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
/** @file ICollector_DX1Collector.cpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
*
|
||||
* See also IAllocator_DX1Collector.cpp for allocator facet
|
||||
**/
|
||||
|
||||
#include "gc/ICollector_DX1Collector.hpp"
|
||||
|
|
|
|||
|
|
@ -7,11 +7,14 @@ set(UTEST_SRCS
|
|||
arena.test.cpp
|
||||
objectmodel.test.cpp
|
||||
Collector.test.cpp
|
||||
random_allocs.cpp
|
||||
)
|
||||
|
||||
if (ENABLE_TESTING)
|
||||
xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS})
|
||||
xo_headeronly_dependency(${UTEST_EXE} randomgen)
|
||||
xo_self_dependency(${UTEST_EXE} xo_alloc2)
|
||||
xo_headeronly_dependency(${UTEST_EXE} indentlog)
|
||||
xo_headeronly_dependency(${UTEST_EXE} xo_facet)
|
||||
xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -7,24 +7,36 @@
|
|||
* see xo-object2/utest
|
||||
**/
|
||||
|
||||
#include "Allocator.hpp"
|
||||
#include "Collector.hpp"
|
||||
#include "random_allocs.hpp"
|
||||
#include "gc/ICollector_DX1Collector.hpp"
|
||||
#include "gc/IAllocator_DX1Collector.hpp"
|
||||
//#include "gc/DX1Collector.hpp"
|
||||
#include <xo/randomgen/xoshiro256.hpp>
|
||||
#include <xo/randomgen/random_seed.hpp>
|
||||
#include <xo/indentlog/print/tag.hpp>
|
||||
#include <xo/indentlog/print/array.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
namespace xo {
|
||||
using xo::mm::AAllocator;
|
||||
using xo::mm::ACollector;
|
||||
using xo::mm::CollectorConfig;
|
||||
using xo::mm::DX1Collector;
|
||||
using xo::mm::ArenaConfig;
|
||||
using xo::mm::generation;
|
||||
using xo::mm::c_max_generation;
|
||||
using xo::facet::with_facet;
|
||||
|
||||
namespace ut {
|
||||
// checklist
|
||||
// - obj<ACollector> constructible [ ]
|
||||
// - obj<ACollector> truthy [ ]
|
||||
// - obj<ACollector> constructible [ ]
|
||||
// - obj<ACollector> truthy [ ]
|
||||
// - obj<ACollector,DX1Collector> constructible [ ]
|
||||
//
|
||||
// - obj<AAllocator,DX1Collector> constructible [ ]
|
||||
// - obj<AAllocator,DX1Collector> allocation
|
||||
|
||||
TEST_CASE("collector-any-null", "[alloc2][gc][ACollector]")
|
||||
{
|
||||
|
|
@ -95,12 +107,68 @@ namespace xo {
|
|||
|
||||
DX1Collector gc = DX1Collector{cfg};
|
||||
|
||||
/* typed collector */
|
||||
/* typed collector -- repr known at compile time */
|
||||
obj<ACollector, DX1Collector> x1(&gc);
|
||||
|
||||
REQUIRE(x1.iface());
|
||||
REQUIRE(x1.data());
|
||||
}
|
||||
|
||||
TEST_CASE("collector-x1-facet-mkobj", "[alloc2][gc]")
|
||||
{
|
||||
ArenaConfig arena_cfg = { .name_ = "_test_unused",
|
||||
.size_ = 4*1024*1024,
|
||||
.store_header_flag_ = true,
|
||||
.header_size_mask_ = 0x0000ffff, };
|
||||
CollectorConfig cfg = { .arena_config_ = arena_cfg,
|
||||
.n_generation_ = 2,
|
||||
.gc_trigger_v_ = {{64*1024, 1024*1024, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0}} };
|
||||
|
||||
DX1Collector gc = DX1Collector{cfg};
|
||||
|
||||
/* typed collector -- repr inferred at compile time */
|
||||
auto x1 = with_facet<ACollector>::mkobj(&gc);
|
||||
|
||||
REQUIRE(x1.iface());
|
||||
REQUIRE(x1.data());
|
||||
}
|
||||
|
||||
TEST_CASE("collector-x1-alloc", "[alloc2][gc]")
|
||||
{
|
||||
ArenaConfig arena_cfg = { .name_ = "_test_unused",
|
||||
.size_ = 4*1024*1024,
|
||||
.store_header_flag_ = true,
|
||||
.header_size_mask_ = 0x0000ffff, };
|
||||
|
||||
/* collector with one generation collapses to a non-generational copying collector */
|
||||
CollectorConfig cfg = { .arena_config_ = arena_cfg,
|
||||
.n_generation_ = 1,
|
||||
.gc_trigger_v_ = {{64*1024, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0}} };
|
||||
|
||||
DX1Collector x1state = DX1Collector{cfg};
|
||||
|
||||
/* typed collector */
|
||||
auto x1gc = with_facet<ACollector>::mkobj(&x1state);
|
||||
auto x1alloc = with_facet<AAllocator>::mkobj(&x1state);
|
||||
|
||||
REQUIRE(x1gc.iface());
|
||||
REQUIRE(x1gc.data());
|
||||
|
||||
REQUIRE(x1alloc.iface());
|
||||
REQUIRE(x1alloc.data());
|
||||
|
||||
rng::Seed<rng::xoshiro256ss> seed;
|
||||
std::cerr << "ratio: seed=" << seed << std::endl;
|
||||
|
||||
auto rng = rng::xoshiro256ss(seed);
|
||||
|
||||
utest::AllocUtil::random_allocs(25, true, &rng, x1alloc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
103
utest/random_allocs.cpp
Normal file
103
utest/random_allocs.cpp
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/** @file random_allocs.cpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
**/
|
||||
|
||||
#include "random_allocs.hpp"
|
||||
#include <xo/indentlog/scope.hpp>
|
||||
#include <xo/indentlog/print/tag.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <map>
|
||||
|
||||
namespace utest {
|
||||
using xo::rng::xoshiro256ss;
|
||||
using xo::facet::obj;
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
using std::uint32_t;
|
||||
using std::byte;
|
||||
|
||||
/* remember an allocation result.
|
||||
* application owns memory in [lo, lo+z)
|
||||
*/
|
||||
struct AllocInfo {
|
||||
AllocInfo() = default;
|
||||
AllocInfo(byte * lo, size_t z) : lo_{lo}, z_{z} {}
|
||||
|
||||
byte * lo() const { return lo_; }
|
||||
byte * hi() const { return lo_ + z_; }
|
||||
|
||||
byte * lo_ = nullptr;
|
||||
size_t z_ = 0;
|
||||
};
|
||||
|
||||
bool
|
||||
AllocUtil::random_allocs(uint32_t n_alloc,
|
||||
bool catch_flag,
|
||||
xoshiro256ss * p_rgen,
|
||||
obj<AAllocator> mm)
|
||||
{
|
||||
scope log(XO_DEBUG(catch_flag), xtag("n-alloc", n_alloc));
|
||||
|
||||
/* track allocs. verify:
|
||||
* - allocs are non-overlapping
|
||||
* - allocs have valid alloc header
|
||||
* - allocs surrounded by guard bytes
|
||||
*
|
||||
* allocs sorted on AllocInfo::lo
|
||||
*/
|
||||
std::map<byte *, AllocInfo> allocs_by_lo_map;
|
||||
/* allocs sorted on AllocInfo::hi */
|
||||
std::map<byte *, AllocInfo*> allocs_by_hi_map;
|
||||
|
||||
for (uint32_t i_alloc = 0; i_alloc < n_alloc; ++i_alloc) {
|
||||
std::normal_distribution<double> ngen{5.0, 1.5};
|
||||
|
||||
double si = ngen(*p_rgen);
|
||||
double zi = ::pow(2.0, si);
|
||||
std::size_t z = ::ceil(zi);
|
||||
|
||||
bool ok_flag = true;
|
||||
|
||||
std::byte * mem = mm.alloc(z);
|
||||
log && log(xtag("i_alloc", i_alloc),
|
||||
xtag("si", si),
|
||||
xtag("zi", zi),
|
||||
xtag("mem", mem));
|
||||
log && log(xtag("used", mm.allocated()),
|
||||
xtag("avail", mm.available()),
|
||||
xtag("commit", mm.committed()),
|
||||
xtag("resv", mm.reserved()));
|
||||
|
||||
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, mem != nullptr);
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, mm.contains(mem));
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, mm.last_error().error_seq_ == 0);
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, mm.last_error().error_ == xo::mm::error::none);
|
||||
|
||||
{
|
||||
auto ix = allocs_by_lo_map.lower_bound(mem);
|
||||
if (ix != allocs_by_lo_map.end()) {
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, (ix->first > mem + z));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto ix = allocs_by_hi_map.upper_bound(mem);
|
||||
if (ix != allocs_by_hi_map.end()) {
|
||||
--ix;
|
||||
REQUIRE_ORFAIL(ok_flag, catch_flag, (ix->first < mem));
|
||||
}
|
||||
}
|
||||
|
||||
allocs_by_lo_map[mem] = AllocInfo(mem, z);
|
||||
allocs_by_hi_map[mem + z] = &(allocs_by_lo_map[mem]);
|
||||
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* end random_allocs.cpp */
|
||||
45
utest/random_allocs.hpp
Normal file
45
utest/random_allocs.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/** @file random_allocs.hpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Allocator.hpp"
|
||||
#include <xo/facet/obj.hpp>
|
||||
#include <xo/randomgen/xoshiro256.hpp>
|
||||
|
||||
namespace utest {
|
||||
|
||||
/* note: trivial REQUIRE() call in else branch bc we still want
|
||||
* catch2 to count assertions when verification succeeds
|
||||
*/
|
||||
# define REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr) \
|
||||
if (catch_flag) { \
|
||||
REQUIRE((expr)); \
|
||||
} else { \
|
||||
REQUIRE(true); \
|
||||
ok_flag &= (expr); \
|
||||
}
|
||||
|
||||
# define REQUIRE_ORFAIL(ok_flag, catch_flag, expr) \
|
||||
REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr); \
|
||||
if (!ok_flag) \
|
||||
return ok_flag
|
||||
|
||||
|
||||
|
||||
struct AllocUtil {
|
||||
using AAllocator = xo::mm::AAllocator;
|
||||
|
||||
/** generate a random sequence of allocations.
|
||||
* verify allocator behavior
|
||||
**/
|
||||
static bool random_allocs(std::uint32_t n_alloc,
|
||||
bool catch_flag,
|
||||
xo::rng::xoshiro256ss * p_rgen,
|
||||
xo::facet::obj<AAllocator> alloc);
|
||||
};
|
||||
}
|
||||
|
||||
/* end random_allocs.hpp */
|
||||
Loading…
Add table
Add a link
Reference in a new issue