# Memory Pools

Memory pools amortize allocation cost for objects that are created in large numbers and typically live for the whole run (or a well-defined phase, such as one circuit load). Instead of calling `malloc`/`new` per object, a pool grabs memory in large blocks and hands out pieces of a block on request. This avoids per-object heap bookkeeping and lets a whole pool be released at once, which is cheaper and simpler than tracking and freeing every object individually. The pool classes are declared in `pool.h` and implemented in `pool.cpp`.

## PoolAllocator

`PoolAllocator` is the untyped base allocator. It hands out raw `void*` regions of a requested size in bytes-worth-of-entries (`count * entrySize`).

Memory is organized into `Block`s, each a single `malloc`ed buffer with a `used` watermark. `PoolAllocator::allocate()` bump-allocates from the current block; when a block cannot satisfy a request it is skipped and the next one is tried.

Growth strategy: when no existing block has room, a new block is appended after the last one. Its size is `lastBlock->size * growthFactor`, or `count * entrySize * growthFactor` if that is larger (so a single oversized request still gets a block big enough to hold it, with room to spare). This gives geometric growth, the same amortized-constant-time argument used by `std::vector`.

Blocks are never freed individually and never reordered, so a `std::forward_list` is used to hold them; iterators to existing blocks stay valid as new ones are appended.

`atBlock` avoids rescanning blocks that are effectively full. Each block counts how many times an allocation attempt failed against it (`failures`). After a successful allocation, `advance()` moves `atBlock` forward past any leading blocks whose failure count has reached `maxFailures`, so later calls to `allocate()` start scanning from there instead of from the beginning of the list every time. This keeps the average cost of `allocate()` low even after many small blocks have filled up.

`clear()` drops all blocks and starts over with one block of `initialCount * entrySize` bytes, the same state as right after construction.

## TypedPoolAllocator\<T, doPlacement\>

`TypedPoolAllocator` wraps `PoolAllocator` to hand out `T*` instead of `void*`, sized as `sizeof(T)` per entry. The `doPlacement` template flag (default `true`) controls whether entries are constructed and destroyed:

- `doPlacement=true`: `allocate(args...)` placement-constructs a `T` in the returned slot with the given constructor arguments; the destructor's turn comes when the pool itself is destroyed or `clear()`ed, at which point every live entry in every block is destructed by walking the block from its base up to `used`.
- `doPlacement=false`: no constructor/destructor calls are made; the pool just reserves raw storage. This is the mode used for `T=char`, where per-element construction is pointless overhead (see `CStringPool` below).

`allocate(count, args...)` (the array form) always default-constructs each of the `count` entries; it does not forward `args` to the per-entry constructor beyond that call site's use in `CStringPool`, which invokes the single-entry raw byte form.

## RecyclingTypedPoolAllocator\<T, doPlacement\>

Adds `free()` to `TypedPoolAllocator`: freed entries go on a `freeEntries` list instead of being returned to the block allocator (individual entries can never be handed back to a `Block`, only reused). `allocate()` first checks `freeEntries`; if one is available, it is popped, destructed and reconstructed in place (when `doPlacement` is set) with the new arguments, and returned. Only when the free list is empty does it fall through to `TypedPoolAllocator::allocate()` for a fresh entry.

This is the allocator to use for objects with a create/destroy churn pattern within a pool's lifetime, e.g. `Circuit::nodePool` (`include/circuit.h`), which allocates and frees `Node` objects as the circuit is elaborated and reloaded.

## CStringPool

`CStringPool` is a pool of null-terminated C strings, built on `TypedPoolAllocator<char, false>` (no per-character construction). `allocate(s)`, `allocate(s, n)`, and `allocate(const std::string&)` copy the string plus its terminating `\0` into freshly reserved space and return a `const char*` to it. Strings cannot be freed individually, only released together when the pool is cleared or destroyed - this matches how it is used: interning identifiers (`Id::stringPool()`, `Id::staticStringPool()` in `identifier.cpp`) and netlist source strings (`FileStack::stringsPool` in `filestack.h`), both of which live for the lifetime of the data structure that owns them rather than being freed one at a time.

`dump()` prints, per block, every string found by walking the block byte-by-byte and splitting on `\0`.
