xo-alloc2: + Allocator::alloc_info()
Also extend unit test
This commit is contained in:
parent
e369bc93f4
commit
ef8ec32a2d
15 changed files with 156 additions and 40 deletions
|
|
@ -6,6 +6,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "AllocatorError.hpp"
|
#include "AllocatorError.hpp"
|
||||||
|
#include "AllocInfo.hpp"
|
||||||
#include "xo/facet/facet_implementation.hpp"
|
#include "xo/facet/facet_implementation.hpp"
|
||||||
#include "xo/facet/typeseq.hpp"
|
#include "xo/facet/typeseq.hpp"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
@ -72,9 +73,15 @@ namespace xo {
|
||||||
/** true iff allocator @p d is responsible for memory at address @p p.
|
/** true iff allocator @p d is responsible for memory at address @p p.
|
||||||
**/
|
**/
|
||||||
virtual bool contains(Copaque d, const void * p) const noexcept = 0;
|
virtual bool contains(Copaque d, const void * p) const noexcept = 0;
|
||||||
|
|
||||||
/** report last error **/
|
/** report last error **/
|
||||||
virtual AllocatorError last_error(Copaque d) const noexcept = 0;
|
virtual AllocatorError last_error(Copaque d) const noexcept = 0;
|
||||||
|
/** fetch alloc info: given memory @p mem previously obtained
|
||||||
|
* from {@ref alloc, @ref super_alloc}, get {tseq, age, size} details
|
||||||
|
* for that allocation.
|
||||||
|
*
|
||||||
|
* Non-const @p d because may stash error details
|
||||||
|
**/
|
||||||
|
virtual AllocInfo alloc_info(Opaque d, value_type mem) const noexcept = 0;
|
||||||
|
|
||||||
/** expand committed space in arena @p d
|
/** expand committed space in arena @p d
|
||||||
* to size at least @p z
|
* to size at least @p z
|
||||||
|
|
|
||||||
|
|
@ -148,19 +148,6 @@ namespace xo {
|
||||||
std::uint8_t size_bits_ = 32;
|
std::uint8_t size_bits_ = 32;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AllocInfo {
|
|
||||||
using size_type = AllocHeader::size_type;
|
|
||||||
|
|
||||||
AllocInfo(const AllocHeaderConfig * p_cfg, const AllocHeader * p_hdr)
|
|
||||||
: p_config_{p_cfg}, p_header_{p_hdr} {}
|
|
||||||
|
|
||||||
std::uint32_t tseq() const noexcept { return p_config_->tseq(*p_header_); }
|
|
||||||
std::uint32_t age() const noexcept { return p_config_->age (*p_header_); }
|
|
||||||
size_type size() const noexcept { return p_config_->size(*p_header_); }
|
|
||||||
|
|
||||||
const AllocHeaderConfig * p_config_ = nullptr;
|
|
||||||
const AllocHeader * p_header_ = nullptr;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@ namespace xo {
|
||||||
header_size_mask,
|
header_size_mask,
|
||||||
/** sub_alloc not preceded by super alloc (or another sub_alloc) **/
|
/** sub_alloc not preceded by super alloc (or another sub_alloc) **/
|
||||||
orphan_sub_alloc,
|
orphan_sub_alloc,
|
||||||
|
/** attempt to call alloc_info for allocator with alloc header feature disabled
|
||||||
|
* (e.g. @ref see ArenaConfig::store_header_flag_)
|
||||||
|
**/
|
||||||
|
alloc_info_disabled,
|
||||||
|
/** attempt to call alloc_info for address not owned by allocator **/
|
||||||
|
alloc_info_address,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AllocatorError {
|
struct AllocatorError {
|
||||||
|
|
|
||||||
|
|
@ -42,12 +42,13 @@ namespace xo {
|
||||||
[[noreturn]] AllocatorError last_error(Copaque) const noexcept override { _fatal(); }
|
[[noreturn]] AllocatorError last_error(Copaque) const noexcept override { _fatal(); }
|
||||||
|
|
||||||
// non-const methods
|
// non-const methods
|
||||||
[[noreturn]] bool expand(Opaque, std::size_t) const noexcept override { _fatal(); }
|
[[noreturn]] AllocInfo alloc_info(Opaque, value_type) const noexcept override { _fatal(); }
|
||||||
[[noreturn]] value_type alloc(Opaque, std::size_t) const override { _fatal(); }
|
[[noreturn]] bool expand(Opaque, std::size_t) const noexcept override { _fatal(); }
|
||||||
[[noreturn]] value_type super_alloc(Opaque, std::size_t) const override { _fatal(); }
|
[[noreturn]] value_type alloc(Opaque, std::size_t) const override { _fatal(); }
|
||||||
[[noreturn]] value_type sub_alloc(Opaque, std::size_t, bool) const override { _fatal(); }
|
[[noreturn]] value_type super_alloc(Opaque, std::size_t) const override { _fatal(); }
|
||||||
[[noreturn]] void clear(Opaque) const override { _fatal(); }
|
[[noreturn]] value_type sub_alloc(Opaque, std::size_t, bool) const override { _fatal(); }
|
||||||
[[noreturn]] void destruct_data(Opaque) const override { _fatal(); }
|
[[noreturn]] void clear(Opaque) const override { _fatal(); }
|
||||||
|
[[noreturn]] void destruct_data(Opaque) const override { _fatal(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[noreturn]] static void _fatal();
|
[[noreturn]] static void _fatal();
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,9 @@ namespace xo {
|
||||||
|
|
||||||
// non-const methods
|
// non-const methods
|
||||||
|
|
||||||
|
AllocInfo alloc_info(Opaque d, value_type mem) const noexcept override {
|
||||||
|
return I::alloc_info(_dcast(d), mem);
|
||||||
|
}
|
||||||
bool expand(Opaque d,
|
bool expand(Opaque d,
|
||||||
std::size_t z) const noexcept override { return I::expand(_dcast(d), z); }
|
std::size_t z) const noexcept override { return I::expand(_dcast(d), z); }
|
||||||
value_type alloc(Opaque d,
|
value_type alloc(Opaque d,
|
||||||
|
|
|
||||||
|
|
@ -25,22 +25,23 @@ namespace xo {
|
||||||
RAllocator() {}
|
RAllocator() {}
|
||||||
RAllocator(Object::DataPtr data) : Object{std::move(data)} {}
|
RAllocator(Object::DataPtr data) : Object{std::move(data)} {}
|
||||||
|
|
||||||
int32_t _typeseq() const noexcept { return O::iface()->_typeseq(); }
|
int32_t _typeseq() const noexcept { return O::iface()->_typeseq(); }
|
||||||
std::string_view name() const noexcept { return O::iface()->name(O::data()); }
|
std::string_view name() const noexcept { return O::iface()->name(O::data()); }
|
||||||
size_type reserved() const noexcept { return O::iface()->reserved(O::data()); }
|
size_type reserved() const noexcept { return O::iface()->reserved(O::data()); }
|
||||||
size_type size() const noexcept { return O::iface()->size(O::data()); }
|
size_type size() const noexcept { return O::iface()->size(O::data()); }
|
||||||
size_type committed() const noexcept { return O::iface()->committed(O::data()); }
|
size_type committed() const noexcept { return O::iface()->committed(O::data()); }
|
||||||
size_type available() const noexcept { return O::iface()->available(O::data()); }
|
size_type available() const noexcept { return O::iface()->available(O::data()); }
|
||||||
size_type allocated() const noexcept { return O::iface()->allocated(O::data()); }
|
size_type allocated() const noexcept { return O::iface()->allocated(O::data()); }
|
||||||
bool contains(const void * p) const noexcept { return O::iface()->contains(O::data(), p); }
|
bool contains(const void * p) const noexcept { return O::iface()->contains(O::data(), p); }
|
||||||
AllocatorError last_error() const noexcept { return O::iface()->last_error(O::data()); }
|
AllocatorError last_error() const noexcept { return O::iface()->last_error(O::data()); }
|
||||||
|
|
||||||
bool expand(size_type z) { return O::iface()->expand(O::data(), z); }
|
value_type alloc(size_type z) noexcept { return O::iface()->alloc(O::data(), z); }
|
||||||
value_type alloc(size_type z) noexcept { return O::iface()->alloc(O::data(), z); }
|
|
||||||
value_type super_alloc(size_type z) noexcept { return O::iface()->super_alloc(O::data(), z); }
|
value_type super_alloc(size_type z) noexcept { return O::iface()->super_alloc(O::data(), z); }
|
||||||
value_type sub_alloc(size_type z,
|
value_type sub_alloc(size_type z,
|
||||||
bool complete_flag) noexcept { return O::iface()->sub_alloc(O::data(),
|
bool complete_flag) noexcept { return O::iface()->sub_alloc(O::data(),
|
||||||
z, complete_flag); }
|
z, complete_flag); }
|
||||||
|
bool expand(size_type z) { return O::iface()->expand(O::data(), z); }
|
||||||
|
AllocInfo alloc_info(value_type mem) { return O::iface()->alloc_info(O::data(), mem); }
|
||||||
|
|
||||||
static bool _valid;
|
static bool _valid;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,18 @@ namespace xo {
|
||||||
/** get header from allocated object address **/
|
/** get header from allocated object address **/
|
||||||
header_type * obj2hdr(void * obj) noexcept;
|
header_type * obj2hdr(void * obj) noexcept;
|
||||||
|
|
||||||
|
/** report alloc book-keeping info for allocation at @p mem
|
||||||
|
*
|
||||||
|
* Require:
|
||||||
|
* 1. @p mem is address returned by allocation on this arena
|
||||||
|
* i.e. by @ref IAllocator_DArena::alloc() or @ref IAllocator_DArena::alloc_super()
|
||||||
|
* 2. @p mem has not been invalidated since it was allocated
|
||||||
|
* i.e. by call to @ref DArena::clear
|
||||||
|
*
|
||||||
|
* Note: non-const, may stash error details
|
||||||
|
**/
|
||||||
|
AllocInfo alloc_info(value_type mem) noexcept;
|
||||||
|
|
||||||
/** discard all allocated memory, return to empty state
|
/** discard all allocated memory, return to empty state
|
||||||
* Promise:
|
* Promise:
|
||||||
* - committed memory unchanged
|
* - committed memory unchanged
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ namespace xo {
|
||||||
static bool contains(const DArena &, const void * p) noexcept;
|
static bool contains(const DArena &, const void * p) noexcept;
|
||||||
static AllocatorError last_error(const DArena &) noexcept;
|
static AllocatorError last_error(const DArena &) noexcept;
|
||||||
|
|
||||||
|
/** retrieve allocation bookkeeping info for @p mem from arena @p d **/
|
||||||
|
static AllocInfo alloc_info(DArena &, value_type mem) noexcept;
|
||||||
/** expand committed space in arena @p d
|
/** expand committed space in arena @p d
|
||||||
* to size at least @p z
|
* to size at least @p z
|
||||||
* In practice will round up to a multiple of @ref page_z_.
|
* In practice will round up to a multiple of @ref page_z_.
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,8 @@ namespace xo {
|
||||||
/** expand gen0 committed size to at least @p z.
|
/** expand gen0 committed size to at least @p z.
|
||||||
**/
|
**/
|
||||||
bool expand(size_type z) noexcept;
|
bool expand(size_type z) noexcept;
|
||||||
|
/** Retreive bookkeeping info for allocation at @p mem. **/
|
||||||
|
AllocInfo alloc_info(value_type mem) noexcept;
|
||||||
|
|
||||||
// ----- book-keeping -----
|
// ----- book-keeping -----
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ namespace xo {
|
||||||
static value_type sub_alloc(DX1Collector & d, size_type z, bool complete) noexcept;
|
static value_type sub_alloc(DX1Collector & d, size_type z, bool complete) noexcept;
|
||||||
/** expand gen0 spaces (both from-space and to-space) **/
|
/** expand gen0 spaces (both from-space and to-space) **/
|
||||||
static bool expand(DX1Collector & d, size_type z) noexcept;
|
static bool expand(DX1Collector & d, size_type z) noexcept;
|
||||||
|
/** fetch allocation bookkeeping info **/
|
||||||
|
static AllocInfo alloc_info(DX1Collector & d, value_type mem) noexcept;
|
||||||
|
|
||||||
/** reset to empty state; clears all generations **/
|
/** reset to empty state; clears all generations **/
|
||||||
static void clear(DX1Collector & d);
|
static void clear(DX1Collector & d);
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,34 @@ namespace xo {
|
||||||
return (header_type *)((byte *)obj - sizeof(header_type));
|
return (header_type *)((byte *)obj - sizeof(header_type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AllocInfo
|
||||||
|
DArena::alloc_info(value_type mem) noexcept
|
||||||
|
{
|
||||||
|
if (!config_.store_header_flag_) [[unlikely]] {
|
||||||
|
++(error_count_);
|
||||||
|
last_error_ = AllocatorError(error::alloc_info_disabled,
|
||||||
|
error_count_,
|
||||||
|
0 /*add_commit_z*/,
|
||||||
|
committed_z_,
|
||||||
|
this->reserved());
|
||||||
|
|
||||||
|
return AllocInfo::error_not_configured(&config_.header_);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte * header_mem = mem - sizeof(AllocHeader);
|
||||||
|
|
||||||
|
if (!this->contains(header_mem)) {
|
||||||
|
++(error_count_);
|
||||||
|
last_error_ = AllocatorError(error::alloc_info_address,
|
||||||
|
error_count_,
|
||||||
|
0 /*add_commit_z*/,
|
||||||
|
committed_z_,
|
||||||
|
this->reserved());
|
||||||
|
}
|
||||||
|
|
||||||
|
return AllocInfo(&config_.header_, (AllocHeader *)header_mem);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
DArena::clear() noexcept
|
DArena::clear() noexcept
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -239,6 +239,24 @@ namespace xo {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AllocInfo
|
||||||
|
DX1Collector::alloc_info(value_type mem) noexcept {
|
||||||
|
for (role ri : role::all()) {
|
||||||
|
for (generation gj{0}; gj < config_.n_generation_; ++gj) {
|
||||||
|
DArena * arena = this->get_space(ri, gj);
|
||||||
|
|
||||||
|
assert(arena);
|
||||||
|
|
||||||
|
if (arena->contains(mem)) {
|
||||||
|
return arena->alloc_info(mem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deliberately attempt on nursery to-space, to capture error info + return sentinel
|
||||||
|
return this->new_space()->alloc_info(mem);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
DX1Collector::reverse_roles(generation g) noexcept {
|
DX1Collector::reverse_roles(generation g) noexcept {
|
||||||
assert(g < config_.n_generation_);
|
assert(g < config_.n_generation_);
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,36 @@ namespace xo {
|
||||||
return s.last_error_;
|
return s.last_error_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AllocInfo
|
||||||
|
IAllocator_DArena::alloc_info(DArena & s, value_type mem) noexcept
|
||||||
|
{
|
||||||
|
return s.alloc_info(mem);
|
||||||
|
|
||||||
|
if (!s.config_.store_header_flag_) [[unlikely]] {
|
||||||
|
++(s.error_count_);
|
||||||
|
s.last_error_ = AllocatorError(error::alloc_info_disabled,
|
||||||
|
s.error_count_,
|
||||||
|
0 /*add_commit_z*/,
|
||||||
|
s.committed_z_,
|
||||||
|
reserved(s));
|
||||||
|
|
||||||
|
return AllocInfo::error_not_configured(&s.config_.header_);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte * header_mem = mem - sizeof(AllocHeader);
|
||||||
|
|
||||||
|
if (!s.contains(header_mem)) {
|
||||||
|
++(s.error_count_);
|
||||||
|
s.last_error_ = AllocatorError(error::alloc_info_address,
|
||||||
|
s.error_count_,
|
||||||
|
0 /*add_commit_z*/,
|
||||||
|
s.committed_z_,
|
||||||
|
reserved(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
return AllocInfo(&s.config_.header_, (AllocHeader*)header_mem);
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
IAllocator_DArena::expand(DArena & s, size_t target_z) noexcept
|
IAllocator_DArena::expand(DArena & s, size_t target_z) noexcept
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,12 @@ namespace xo {
|
||||||
return d.expand(z);
|
return d.expand(z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AllocInfo
|
||||||
|
IAllocator_DX1Collector::alloc_info(DX1Collector & d, value_type mem) noexcept
|
||||||
|
{
|
||||||
|
return d.alloc_info(mem);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
IAllocator_DX1Collector::clear(DX1Collector & d)
|
IAllocator_DX1Collector::clear(DX1Collector & d)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,15 @@
|
||||||
**/
|
**/
|
||||||
|
|
||||||
#include "random_allocs.hpp"
|
#include "random_allocs.hpp"
|
||||||
|
#include "padding.hpp"
|
||||||
#include <xo/indentlog/scope.hpp>
|
#include <xo/indentlog/scope.hpp>
|
||||||
#include <xo/indentlog/print/tag.hpp>
|
#include <xo/indentlog/print/tag.hpp>
|
||||||
#include <catch2/catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
namespace utest {
|
namespace utest {
|
||||||
|
using xo::mm::AllocInfo;
|
||||||
|
using xo::mm::padding;
|
||||||
using xo::rng::xoshiro256ss;
|
using xo::rng::xoshiro256ss;
|
||||||
using xo::facet::obj;
|
using xo::facet::obj;
|
||||||
using xo::scope;
|
using xo::scope;
|
||||||
|
|
@ -20,9 +23,9 @@ namespace utest {
|
||||||
/* remember an allocation result.
|
/* remember an allocation result.
|
||||||
* application owns memory in [lo, lo+z)
|
* application owns memory in [lo, lo+z)
|
||||||
*/
|
*/
|
||||||
struct AllocInfo {
|
struct Alloc {
|
||||||
AllocInfo() = default;
|
Alloc() = default;
|
||||||
AllocInfo(byte * lo, size_t z) : lo_{lo}, z_{z} {}
|
Alloc(byte * lo, size_t z) : lo_{lo}, z_{z} {}
|
||||||
|
|
||||||
byte * lo() const { return lo_; }
|
byte * lo() const { return lo_; }
|
||||||
byte * hi() const { return lo_ + z_; }
|
byte * hi() const { return lo_ + z_; }
|
||||||
|
|
@ -44,11 +47,11 @@ namespace utest {
|
||||||
* - allocs have valid alloc header
|
* - allocs have valid alloc header
|
||||||
* - allocs surrounded by guard bytes
|
* - allocs surrounded by guard bytes
|
||||||
*
|
*
|
||||||
* allocs sorted on AllocInfo::lo
|
* allocs sorted on Alloc::lo
|
||||||
*/
|
*/
|
||||||
std::map<byte *, AllocInfo> allocs_by_lo_map;
|
std::map<byte *, Alloc> allocs_by_lo_map;
|
||||||
/* allocs sorted on AllocInfo::hi */
|
/* allocs sorted on Alloc::hi */
|
||||||
std::map<byte *, AllocInfo*> allocs_by_hi_map;
|
std::map<byte *, Alloc*> allocs_by_hi_map;
|
||||||
|
|
||||||
for (uint32_t i_alloc = 0; i_alloc < n_alloc; ++i_alloc) {
|
for (uint32_t i_alloc = 0; i_alloc < n_alloc; ++i_alloc) {
|
||||||
std::normal_distribution<double> ngen{5.0, 1.5};
|
std::normal_distribution<double> ngen{5.0, 1.5};
|
||||||
|
|
@ -90,10 +93,18 @@ namespace utest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allocs_by_lo_map[mem] = AllocInfo(mem, z);
|
allocs_by_lo_map[mem] = Alloc(mem, z);
|
||||||
allocs_by_hi_map[mem + z] = &(allocs_by_lo_map[mem]);
|
allocs_by_hi_map[mem + z] = &(allocs_by_lo_map[mem]);
|
||||||
|
|
||||||
|
/* verify we can recover alloc info */
|
||||||
|
AllocInfo info = mm.alloc_info(mem);
|
||||||
|
|
||||||
|
REQUIRE_ORFAIL(ok_flag, catch_flag, info.is_valid());
|
||||||
|
REQUIRE_ORFAIL(ok_flag, catch_flag, info.size() == padding::with_padding(z));
|
||||||
|
/* age isn't configured -> 0 = sentinel */
|
||||||
|
REQUIRE_ORFAIL(ok_flag, catch_flag, info.age() == 0);
|
||||||
|
/* tseq isn't confrigured -> 0 = sentinel */
|
||||||
|
REQUIRE_ORFAIL(ok_flag, catch_flag, info.tseq() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue