xo-alloc2 : work on X1Collector unit test [WIP]

This commit is contained in:
Roland Conybeare 2025-12-15 22:43:21 -05:00
commit 1fd5d544f2
14 changed files with 542 additions and 41 deletions

View file

@ -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;
///@}

View file

@ -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 **/

View file

@ -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 **/

View file

@ -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; }

View file

@ -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;

View file

@ -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*/

View file

@ -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*/

View file

@ -393,7 +393,7 @@ namespace xo {
void
IAllocator_DArena::clear(DArena & s)
{
s.free_ = s.lo_;
s.clear();
//s.checkpoint_ = s.lo_;
}

View file

@ -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*/

View file

@ -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"

View file

@ -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()

View file

@ -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
View 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
View 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 */