[M0-CORE-05] Memory pools: ArenaPool<T> + Pool<T> - #5
Merged
Merged
Conversation
PRD §10.4 / §9.1 S-2: engine-owned, budgeted, accounted pools. - laige::ArenaPool<T>: arena-scoped, bump-only, reset-per-frame; slots valid until the next reset(); O(inUse) reset, idempotent. - laige::Pool<T>: stable generation-checked handles (CPP-007); LIFO free list, O(1) create/destroy; generation bump on free makes stale handles undetectable-free (2^32 wrap documented). - Both: declared budget fixed at construction (S-6), overflow -> ErrorCode::BudgetExhausted (never grows silently); one backing allocation at construction; O(1) allocation-free hot paths; move-only (moved-from = valid empty pool); destructor destroys all live elements (no leak). - Accounting: laige::PoolStats (counts, peak, churn, bytes) that the M1 profiler pulls; no registration/callback (CORE-004). - Element storage is a plain ElementSlot<T> (alignas(T) std::byte[]) — not std::aligned_storage (deprecated C++23+; GCC 16's C++26-era libstdc++ makes the outer class an empty 1-byte wrapper). - Header-only; no new error codes; pools do not log (the owning system logs BudgetExhausted per G-R1/LOG-002). Docs: docs/api/pools.md (AGENTS §9 contract); module README + building.md status updated; roadmap step recorded with Decision/Verify/Size. Verified locally 2026-09-11: 11/11 ctest on GCC 16.2.1 (static, shared, ASan+UBSan, TSan) and Clang 22.1.8 trees, zero warnings; 25 GTest cases in ctest -R pools (exhaustion -> Status error, reset semantics, generation-checked stale handles incl. a forked-child SIGABRT assert proof on POSIX, leak-free destruction counters); include-lint real-tree check green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements roadmap step M0-CORE-05 (PRD §10.4 memory model, §9.1 S-2 "no per-frame heap in the safe API"):
laige::ArenaPool<T>andlaige::Pool<T>in a new header-onlysrc/laige-core/include/laige/pools.h.What
laige::ArenaPool<T>— arena-scoped, bump-only, budgeted storage: one contiguous aligned block;create()advances a cursor,reset()(the per-frame release) destroys the live prefix and rewinds it (O(inUse), idempotent). Slots areuint32_t, valid until the nextreset(); no in-frame recycling (that isPool's job).laige::Pool<T>— stable-handle pool (CPP-007):handle = {uint32 index, uint32 generation}. LIFO free list, O(1) create/destroy;destroy()bumps the slot's generation so every stale handle failsisValid()(the one 2^32-wrap case is defined unsigned wrap, documented in the header).clear()is O(capacity).Options{capacity}, S-6/API-006) — overflow →ErrorCode::BudgetExhausted(4, already registered; no new codes),destroy()on invalid/stale handle →ErrorCode::InvalidArgument(2). One backing allocation at construction (setup path); every hot-path op O(1), allocation-free, lock-free, noexcept (PERF-003/006). Move-only (O(1); a moved-from pool is a valid empty pool); destructor destroys every live element (no leak). The pool does not log — the owning system logs the failure under its subsystem name (LOG-002, G-R1), keeping a failingcreate()a branch on the cold path.stats()returnslaige::PoolStats— element counts,peakInUse(peak tracking),totalCreated(G-R4 churn), and bytes (capacity/in-use, incl. per-slot bookkeeping forPool). The pool publishes; the profiler pulls — no registration/callback yet (CORE-004: the sink interface lands with the M1 profiler).ElementSlot<T>(alignas(T) std::byte[...]) instead ofstd::aligned_storage— deprecated since C++23, and GCC 16's C++26-era libstdc++ makes the outer class an empty 1-byte wrapper (using it directly would silently allocate 1-byte slots; caught by the byte-accounting tests during development).Docs
docs/api/pools.md(ownership, threading, complexity/allocations, determinism, handle contract, Performance section, performant example + misuse warnings).src/laige-core/README.mdanddocs/getting-started/building.mdstatus updated (incl. the newpoolsCTest entry).Verification (local, 2026-09-11)
ctest -R pools: 25 GTest cases green —ArenaPoolBasics,ArenaPoolBudget,PoolBasics,PoolBudget,PoolStale,PoolDestruction,PoolStats,PoolMove.Statuserror (BudgetExhausted), no crash, no silent growth, and reusability after reset/destroy.isValid(); generation bump verified (gen+1on reuse); double-destroy →InvalidArgumentwith no double free; the stale-handleat()assert is proven in a forked child dying on SIGABRT (POSIX jobs; Windows skips with a reason).build(static),build-shared,build-asan(ASan+UBSan fatal),build-tsan(TSanhalt_on_error=1), and a Clang 22.1.8 tree — zero warnings under the NFR-8.10 policy (-Wall -Werror -fno-exceptions -fno-rtti).The CI lane will additionally cover the P0 Windows job (MSVC compile of the new header) and macOS.