diff --git a/xo-alloc2/include/xo/alloc2/alloc/AllocHeader.hpp b/xo-alloc2/include/xo/alloc2/alloc/AllocHeader.hpp new file mode 100644 index 00000000..663c443e --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/alloc/AllocHeader.hpp @@ -0,0 +1,167 @@ +/** @file AllocHeader.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include +#include +#include + +namespace xo { + namespace mm { + struct AllocHeader { + using repr_type = std::uintptr_t; + using size_type = std::size_t; + + explicit AllocHeader(repr_type x) : repr_{x} {} + +#ifdef OBSOLETE + std::uint32_t tseq(const AllocHeaderConfig & cfg) const noexcept { + return cfg.tseq(repr_); + } + + std::uint32_t age(const AllocHeaderConfig & cfg) const noexcept { + return cfg.age(repr_); + } + + size_type size(const AllocHeaderConfig & cfg) const noexcept { + return cfg.size(repr_); + } +#endif + + repr_type repr_; + }; + + static_assert(sizeof(AllocHeader) == sizeof(AllocHeader::repr_type)); + static_assert(std::is_standard_layout_v); + + /* + * Each allocation is preceded by a 64-bit header. + * Header is split into 3 configurable-width bit fields, + * labelled (from hi to lo bit order) {tseq, age, size}. + * + * 1. tseq. seq# identifying object types; needed for gc. + * 2. gen. age cohort; increases when alloc survives gc. + * 3. size. alloc size. + * + * Arena allocator only uses size. + * X1 collector uses {tseq, gen, size} + * + * alloc header + * + * TTTTTTTTTTTTGGGGGZZZZZZZZZZZZ + * < tseq >< size > + * + * masking + * + * ..432107654321076543210 bit + * + * > < .gen_bits + * 0..............01111111 gen_mask_unshifted + * 0..011111110..........0 gen_mask_shifted + * > < gen_shift + */ + struct AllocHeaderConfig { + using repr_type = AllocHeader; + + AllocHeaderConfig() = default; + AllocHeaderConfig(std::uint8_t t, std::uint8_t a, std::uint8_t z) noexcept + : tseq_bits_{t}, age_bits_{a}, size_bits_{z} {} + + std::uint64_t tseq_mask() const noexcept { + // e.g. + // FF FF FF 00 00 00 00 00 + // with tseq_bits=24, age_bits=8, size_bits=32 + // + return ((1ul << tseq_bits_) - 1) << (age_bits_ + size_bits_); + } + + std::uint64_t age_mask() const noexcept { + // e.g. + // 00 00 00 FF 00 00 00 00 + // with age_bits=8, size_bits=32 + // + return ((1ul << age_bits_) - 1) << size_bits_; + } + + std::uint64_t size_mask() const noexcept { + // e.g. + // 00 00 00 00 FF FF FF FF + // with size_bits=32 + // + return ((1ul << size_bits_) - 1); + } + + /** extract type id from alloc header @p hdr **/ + std::uint32_t tseq(repr_type hdr) const noexcept { + // e.g. + // 0x302010 + // for header + // 30 20 10 -- -- -- -- -- + // with tseq_bits_ = 24, age_bits_ + size_bits_ = 40 + // + return (hdr.repr_ & tseq_mask()) >> (age_bits_ + size_bits_); + } + + /** extract age from alloc header @p hdr **/ + std::uint32_t age(repr_type hdr) const noexcept { + // e.g. + // 0xa0 + // for header + // -- -- -- a0 -- -- -- -- + // with age_bits_ = 8, size_bits_ = 32 + // + return (hdr.repr_ & age_mask()) >> size_bits_; + } + + /** extract size from alloc header @p hdr **/ + std::size_t size(repr_type hdr) const noexcept { + // e.g. + // 0x01020300 + // for header + // -- -- -- -- 01 02 03 00 + // with size_bits_ = 32 + // + return (hdr.repr_ & size_mask()); + } + + /** true iff sentinel tseq, flagging a forwarding pointer **/ + bool is_forwarding_tseq(repr_type hdr) const noexcept { + // e.g. + // 0xFFFFFF + // i.e. header + // FF FF FF -- -- -- -- -- + // with tseq_bits_ = 24, age_bits + size_bits_ = 40 + // + return (hdr.repr_ & tseq_mask()) == tseq_mask(); + } + + bool is_size_enabled() const noexcept { return size_bits_ > 0; } + + /** number of bits for tseq **/ + std::uint8_t tseq_bits_ = 24; + /** number of bits for age **/ + std::uint8_t age_bits_ = 8; + /** number of bits for size **/ + 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; + }; + } +} + +/* end AllocHeader.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/arena/ArenaConfig.hpp b/xo-alloc2/include/xo/alloc2/arena/ArenaConfig.hpp index 950f9d36..5ba01c44 100644 --- a/xo-alloc2/include/xo/alloc2/arena/ArenaConfig.hpp +++ b/xo-alloc2/include/xo/alloc2/arena/ArenaConfig.hpp @@ -5,6 +5,7 @@ #pragma once +#include "alloc/AllocHeader.hpp" #include "alloc/AllocatorError.hpp" #include #include @@ -47,9 +48,13 @@ namespace xo { * present in arena **/ bool store_header_flag_ = false; +#ifdef OBSOLETE /** number of bits to represent allocation size **/ std::uint64_t header_size_bits_ = 32; std::uint64_t header_size_mask_ = (1ul << header_size_bits_) - 1; +#endif + /** configuration for per-alloc header **/ + AllocHeaderConfig header_; /** true to enable debug logging **/ bool debug_flag_ = false; diff --git a/xo-alloc2/include/xo/alloc2/arena/DArena.hpp b/xo-alloc2/include/xo/alloc2/arena/DArena.hpp index c12f3e1b..dc95b0d7 100644 --- a/xo-alloc2/include/xo/alloc2/arena/DArena.hpp +++ b/xo-alloc2/include/xo/alloc2/arena/DArena.hpp @@ -10,17 +10,6 @@ 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 @@ -51,7 +40,7 @@ namespace xo { /** @brief a contiguous memory range **/ using range_type = std::pair; /** @brief type for allocation header (if enabled) **/ - using header_type = std::uint64_t; + using header_type = AllocHeader; //std::uint64_t; ///@} diff --git a/xo-alloc2/include/xo/alloc2/gc/DX1Collector.hpp b/xo-alloc2/include/xo/alloc2/gc/DX1Collector.hpp index ce802f4e..aed77e98 100644 --- a/xo-alloc2/include/xo/alloc2/gc/DX1Collector.hpp +++ b/xo-alloc2/include/xo/alloc2/gc/DX1Collector.hpp @@ -8,6 +8,7 @@ #include "arena/ArenaConfig.hpp" #include "arena/DArena.hpp" #include "gc/generation.hpp" +#include "gc/object_age.hpp" #include "gc/role.hpp" #include #include @@ -40,6 +41,7 @@ namespace xo { struct CollectorConfig { using size_type = std::size_t; +#ifdef OBSOLETE // get from arena_config_.header_ /* * alloc header * TTTTTTTTTTTTGGGGGZZZZZZZZZZZZ @@ -63,6 +65,11 @@ namespace xo { constexpr std::uint64_t tseq_shift() const; constexpr std::uint64_t tseq_mask_unshifted() const; constexpr std::uint64_t tseq_mask_shifted() const; +#endif + + generation age2gen(object_age age) const noexcept { + return generation(age % n_survive_threshold_); + } public: // ----- Instance Variables ----- @@ -175,7 +182,7 @@ namespace xo { /** get allocation size from header **/ std::size_t header2size(header_type hdr) const noexcept; /** get generation counter from alloc header **/ - generation header2gen(header_type hdr) const noexcept; + object_age header2age(header_type hdr) const noexcept; /** get tseq from alloc header **/ uint32_t header2tseq(header_type hdr) const noexcept; diff --git a/xo-alloc2/include/xo/alloc2/gc/object_age.hpp b/xo-alloc2/include/xo/alloc2/gc/object_age.hpp new file mode 100644 index 00000000..ae876bda --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/object_age.hpp @@ -0,0 +1,32 @@ +/** @file object_age.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include + +namespace xo { + namespace mm { + /** hard maximum remembered object age **/ + static constexpr uint32_t c_max_object_age = 127; + + /** @class object_age + * @brief type-safe object age + * + * Object age measured in number of garbage collections survived. + **/ + struct object_age { + using value_type = std::uint32_t; + + explicit object_age(value_type x) : value_{x} {} + + operator value_type() const { return value_; } + + std::uint32_t value_; + }; + } +} + +/* end object_age.hpp */ diff --git a/xo-alloc2/src/alloc2/DX1Collector.cpp b/xo-alloc2/src/alloc2/DX1Collector.cpp index 51b516e9..5d573e8b 100644 --- a/xo-alloc2/src/alloc2/DX1Collector.cpp +++ b/xo-alloc2/src/alloc2/DX1Collector.cpp @@ -7,6 +7,7 @@ #include "arena/IAllocator_DArena.hpp" #include "gc/DX1Collector.hpp" #include "gc/generation.hpp" +#include "gc/object_age.hpp" #include #include #include @@ -23,6 +24,7 @@ namespace xo { } #endif +#ifdef OBSOLETE constexpr std::uint64_t CollectorConfig::gen_shift() const { return arena_config_.header_size_bits_; @@ -37,6 +39,7 @@ namespace xo { CollectorConfig::gen_mask_shifted() const { return gen_mask_unshifted() << arena_config_.header_size_bits_; } +#endif #ifdef NOT_USING constexpr std::uint64_t @@ -45,6 +48,7 @@ namespace xo { } #endif +#ifdef OBSOLETE constexpr std::uint64_t CollectorConfig::tseq_shift() const { return gen_bits_ + arena_config_.header_size_bits_; @@ -59,6 +63,7 @@ namespace xo { CollectorConfig::tseq_mask_shifted() const { return tseq_mask_unshifted() << (gen_bits_ + arena_config_.header_size_bits_); } +#endif // ----- GCRunState ----- @@ -84,7 +89,9 @@ namespace xo { DX1Collector::DX1Collector(const CollectorConfig & cfg) : config_{cfg} { - assert(config_.arena_config_.header_size_bits_ + config_.gen_bits_ + config_.tseq_bits_ <= 64); + assert(config_.arena_config_.header_.size_bits_ + + config_.arena_config_.header_.age_bits_ + + config_.arena_config_.header_.tseq_bits_ <= 64); for (uint32_t igen = 0, ngen = cfg.n_generation_; igen < ngen; ++igen) { space_storage_[0][igen] = DArena::map(cfg.arena_config_); @@ -177,25 +184,25 @@ namespace xo { size_type DX1Collector::header2size(header_type hdr) const noexcept { - uint32_t z = (hdr & config_.arena_config_.header_size_mask_); + uint32_t z = config_.arena_config_.header_.size(hdr); return z; } - generation - DX1Collector::header2gen(header_type hdr) const noexcept + object_age + DX1Collector::header2age(header_type hdr) const noexcept { - uint32_t g = (hdr & config_.gen_mask_shifted()) >> config_.gen_shift(); + uint32_t age = config_.arena_config_.header_.age(hdr); - assert(g < c_max_generation); + assert(age < c_max_object_age); - return generation(g); + return object_age(age); } uint32_t DX1Collector::header2tseq(header_type hdr) const noexcept { - uint32_t tseq = (hdr & config_.tseq_mask_shifted()) >> config_.tseq_shift(); + uint32_t tseq = config_.arena_config_.header_.tseq(hdr); return tseq; } @@ -203,8 +210,8 @@ namespace xo { bool DX1Collector::is_forwarding_header(header_type hdr) const noexcept { - /** all 1 bits to flag forwarding pointer **/ - return header2tseq(hdr) == config_.tseq_mask_shifted(); + /** forwarding pointer encoded as sentinel tseq **/ + return config_.arena_config_.header_.is_forwarding_tseq(hdr); } auto diff --git a/xo-alloc2/src/alloc2/IAllocator_DArena.cpp b/xo-alloc2/src/alloc2/IAllocator_DArena.cpp index cc61c8c5..82b9cc8e 100644 --- a/xo-alloc2/src/alloc2/IAllocator_DArena.cpp +++ b/xo-alloc2/src/alloc2/IAllocator_DArena.cpp @@ -331,7 +331,7 @@ namespace xo { if (store_header_flag) { - if ((s.config_.header_size_mask_ & z0) == z0) [[likely]] { + if (s.config_.header_.is_size_enabled()) [[likely]] { hz = sizeof(header); } else { /* req_z doesn't fit in configured header_size_mask bits */ @@ -362,7 +362,7 @@ namespace xo { /* and rembering for subsequent * sub_alloc() */ - s.last_header_ = (uint64_t *)s.free_; + s.last_header_ = (AllocHeader *)s.free_; } } diff --git a/xo-alloc2/src/alloc2/ICollector_DX1Collector.cpp b/xo-alloc2/src/alloc2/ICollector_DX1Collector.cpp index 2d8adaca..6be5696e 100644 --- a/xo-alloc2/src/alloc2/ICollector_DX1Collector.cpp +++ b/xo-alloc2/src/alloc2/ICollector_DX1Collector.cpp @@ -118,7 +118,7 @@ namespace xo { DArena::header_type alloc_hdr = *p_header; /* recover allocation size */ - std::size_t alloc_z = some_arena->config_.header_size_mask_ & alloc_hdr; + std::size_t alloc_z = some_arena->config_.header_.size(alloc_hdr); /* need to be able to fit forwarding pointer * in place of forwarded object. @@ -176,7 +176,9 @@ namespace xo { // reversed: forwarding pointers are located in from-space and // refer to to-space. - generation g = d.header2gen(alloc_hdr); + object_age age = d.header2age(alloc_hdr); + + generation g = d.config_.age2gen(age); assert(d.runstate_.is_running()); diff --git a/xo-alloc2/utest/Collector.test.cpp b/xo-alloc2/utest/Collector.test.cpp index 861c425b..93b7faaa 100644 --- a/xo-alloc2/utest/Collector.test.cpp +++ b/xo-alloc2/utest/Collector.test.cpp @@ -25,6 +25,7 @@ namespace xo { using xo::mm::CollectorConfig; using xo::mm::DX1Collector; using xo::mm::ArenaConfig; + using xo::mm::AllocHeaderConfig; using xo::mm::generation; using xo::mm::c_max_generation; using xo::facet::with_facet; @@ -53,7 +54,7 @@ namespace xo { ArenaConfig arena_cfg = { .name_ = "_test_unused", .size_ = 4*1024*1024, .store_header_flag_ = true, - .header_size_mask_ = 0x0000ffff, }; + .header_ = AllocHeaderConfig(0, 0, 16), }; CollectorConfig cfg = { .arena_config_ = arena_cfg, .n_generation_ = 2, .gc_trigger_v_ = {{64*1024, 1024*1024, 0, 0, @@ -97,7 +98,7 @@ namespace xo { ArenaConfig arena_cfg = { .name_ = "_test_unused", .size_ = 4*1024*1024, .store_header_flag_ = true, - .header_size_mask_ = 0x0000ffff, }; + .header_ = AllocHeaderConfig(0, 0, 16), }; CollectorConfig cfg = { .arena_config_ = arena_cfg, .n_generation_ = 2, .gc_trigger_v_ = {{64*1024, 1024*1024, 0, 0, @@ -119,7 +120,7 @@ namespace xo { ArenaConfig arena_cfg = { .name_ = "_test_unused", .size_ = 4*1024*1024, .store_header_flag_ = true, - .header_size_mask_ = 0x0000ffff, }; + .header_ = AllocHeaderConfig(0, 0, 16), }; CollectorConfig cfg = { .arena_config_ = arena_cfg, .n_generation_ = 2, .gc_trigger_v_ = {{64*1024, 1024*1024, 0, 0, @@ -141,7 +142,7 @@ namespace xo { ArenaConfig arena_cfg = { .name_ = "_test_unused", .size_ = 4*1024*1024, .store_header_flag_ = true, - .header_size_mask_ = 0x0000ffff, }; + .header_ = AllocHeaderConfig(0, 0, 16), }; /* collector with one generation collapses to a non-generational copying collector */ CollectorConfig cfg = { .arena_config_ = arena_cfg, diff --git a/xo-alloc2/utest/arena.test.cpp b/xo-alloc2/utest/arena.test.cpp index ae4c2dd0..254bd60f 100644 --- a/xo-alloc2/utest/arena.test.cpp +++ b/xo-alloc2/utest/arena.test.cpp @@ -20,7 +20,9 @@ namespace xo { using xo::mm::IAllocator_Xfer; using xo::mm::AllocatorError; using xo::mm::DArena; + using xo::mm::AllocHeaderConfig; using xo::mm::ArenaConfig; + using xo::mm::AllocHeader; using xo::mm::padding; using xo::mm::error; using xo::facet::obj; @@ -176,14 +178,14 @@ namespace xo { TEST_CASE("allocator-alloc-2", "[alloc2][Allocator]") { - using header_type = AAllocator::header_type; + using header_type = AllocHeader; /* typed allocator a1o, with object header */ ArenaConfig cfg { .name_ = "testarena", .size_ = 64*1024, .store_header_flag_ = true, /* up to 4GB */ - .header_size_mask_ = 0xffffffff, + .header_ = AllocHeaderConfig(0, 0, 32), .debug_flag_ = false, }; DArena arena = DArena::map(cfg); @@ -202,7 +204,8 @@ namespace xo { header_type* header = (header_type*)(m0 - sizeof(header_type)); REQUIRE(a1o.contains(header)); - REQUIRE(((*header) & cfg.header_size_mask_) == padding::with_padding(z0)); + REQUIRE(cfg.header_.size(*header) == padding::with_padding(z0)); + //REQUIRE(((*header) & cfg.header_size_mask_) == padding::with_padding(z0)); REQUIRE(a1o.last_error().error_ == error::none); REQUIRE(a1o.last_error().error_seq_ == 0); REQUIRE(a1o.allocated() >= z0); @@ -214,7 +217,7 @@ namespace xo { TEST_CASE("allocator-alloc-3", "[alloc2][Allocator]") { - using header_type = AAllocator::header_type; + using header_type = AllocHeader; /* typed allocator a1o, with object header + guard bytes */ ArenaConfig cfg { .name_ = "testarena", @@ -223,7 +226,7 @@ namespace xo { .guard_byte_ = 0xfd, .store_header_flag_ = true, /* up to 4GB */ - .header_size_mask_ = 0xffffffff, + .header_ = AllocHeaderConfig(0, 0, 32), .debug_flag_ = false, }; DArena arena = DArena::map(cfg); @@ -254,7 +257,8 @@ namespace xo { REQUIRE(a1o.contains(guard0)); REQUIRE(a1o.contains(header)); - REQUIRE(((*header) & cfg.header_size_mask_) == padding::with_padding(z0)); + REQUIRE(cfg.header_.size(*header) == padding::with_padding(z0)); + //REQUIRE(((*header) & cfg.header_size_mask_) == padding::with_padding(z0)); REQUIRE(a1o.last_error().error_ == error::none); REQUIRE(a1o.last_error().error_seq_ == 0);