# Topology

> ForkUnion for C++

A machine is described along two independent axes.
A __compute domain__ is a bindable group of cores sharing a Quality-of-Service class _and_ locality — it is what a pool spawns onto, and the index a worker callback receives.
A __memory domain__ is a bank of memory with its own capacity and access cost — it is what the allocator targets, may be _cpuless_ like a CXL expander, and may serve several compute domains at once.
The axes stay separate because they don't line up: performance and efficiency cores often share one memory controller, and equally-fast cores are often split across cache clusters or sockets.

Each axis carries a __level__, a dense ordinal grouping domains of like performance.
Levels can be fewer than domains, since several domains may share one, and the two axes count in opposite directions: compute levels grow with performance, memory levels grow with _distance_, placing HBM below DDR and CXL above.
Two objects split the answers by provenance, not by axis: a `Topology` holds what the platform __declares__ — domains, cores, QoS classes, volumes, on both axes — while a `Fabric` holds what ForkUnion __observes__: per-edge latencies, bandwidths, and distances, and the per-medium tiers derived from them.
The pipeline is `harvest` all the way down: a `Topology` harvests the declared structure from the OS and stays immutable, a pool spawns on it, and a `Fabric` then harvests the observed performance from the silicon through that pool's pinned workers, pointer-chasing and streaming every reachable edge.
A memory level is a property of the medium, independent of the querying core: it keys on the best bandwidth any initiator sustains to the pool, ties split by the best latency, so a 3 TB/s HBM pool outranks DDR even at equal latency.

|                   | Compute axis            | Memory axis            |
| :---------------- | :---------------------- | :--------------------- |
| Count domains     | `compute_domains_count` | `memory_domains_count` |
| Count levels      | `compute_levels_count`  | `memory_levels_count`  |
| Level of a domain | `compute_level_in`      | `memory_level_in`      |
| Faster means      | __higher__              | __lower__              |

Names are spelled here as in Rust, which Mojo matches; C prefixes them with `fu_`, and Zig spells them in camelCase.
Rust, Zig, and Mojo all carry the domain coordinates as distinct types — `ComputeDomain`, `MemoryDomain`, and `MemoryDomainId` — while C passes plain indices.
Domain counts and compute levels answer from the `Topology`; memory levels and the per-edge magnitudes answer from a harvested `Fabric`, which C prefixes with `fu_fabric_`.

Beyond the level ordinals, two magnitudes describe a compute domain, both best-effort.
`compute_capacity_in` forwards the kernel's own rating of a core, read from `/sys/devices/system/cpu/cpuN/cpu_capacity`, normalized so the fastest core on the machine reads 1024.
Linux publishes that file on `arm64` and on Intel hybrids, and nothing like it exists on Windows or Apple, which read 0.
A kernel that rates every core alike rates nothing, so a tie also reads 0; Linux then splits the classes by their core PMUs, `cpu_core` and `cpu_atom` on Intel hybrids.
`compute_cache_bytes_per_core_in` reports each logical core's share of the deepest cache confined to a domain: one instance's bytes over the cores sharing it.
It is enumerated exactly rather than measured — from the per-core cache hierarchy in `sysfs` on Linux, CPUID leaves elsewhere on x86, `sysctl` on Apple, and the cache relationships on Windows.
Domains of identical throughput can sit behind very differently sized caches, so neither value follows from the other.
Treat both as hints, present only where the hardware volunteers them, and never as a number a program requires.

The memory axis carries its own magnitudes, all answered by the harvested `Fabric`.
`memory_distance` gives the relative cost of reaching a memory domain from a compute domain — 10 for local, higher for remote, on ACPI SLIT's scale but from measured ratios — while `memory_bandwidth` and `memory_latency` give the observed saturated read bandwidth and dependent-load latency per edge.
Back on the `Topology`, `volume_ram` and `volume_ram_in` report installed RAM, total or per domain, while `huge_pages_count_in` and `volume_huge_pages_in` report the free huge pages a domain can still back an allocation with, as a count or in bytes, and `huge_pages_count` and `volume_huge_pages` total them.
Like the compute magnitudes, these are best-effort: fabric queries read 0 before a harvest or on edges no pinned worker could reach.

Finally, `local_memory_of` bridges the axes, naming the memory domain a given compute domain should allocate from.
Where no topology is harvested, every query degrades to a single compute domain and a single memory domain rather than failing, so these loops need no conditional compilation.

`#include <forkunion/topology.hpp>`

`#include <forkunion/distributed.hpp>`

`#include <forkunion/types.hpp>`

`#include <forkunion/logging.hpp>`

## memory_domain_id_t

The OS's NUMA node id - on Linux, an `N` for which `/sys/devices/system/node/nodeN` exists.

```cpp
enum memory_domain_id_t
```

## core_id_t

Opaque logical-processor id; on Windows it packs a group and a bit.

```cpp
enum core_id_t
```

## socket_id_t

Physical CPU socket, or -1 where the OS will not say.

```cpp
enum socket_id_t
```

## compute_domain_index_t

A position in `[machine_topology](#machine_topology)'s` array of __compute__ domains, in [0, compute_domains_count).

```cpp
enum compute_domain_index_t
```

These are plain enums with a fixed underlying type, deliberately not `enum class`. The implicit widening to `std::size_t` survives, so comparisons and array subscripts work as on integers; what does __not__ survive is passing one where the other is expected, because no conversion exists between two enumeration types. Minting one costs a `static_cast`, which is the point: it marks the spot where an untyped integer becomes a claim about __which__ axis it indexes.

This is not hypothetical. `RoundRobinVec` handed a compute-domain index to an allocator expecting a memory-domain index, and `nbody.cpp` did the same to an array of per-node replicas. Both compiled, both ran on every machine where the two counts happened to match, and both broke on the first chip with three compute domains over one memory domain.

The OS IDs above - cores, memory domains and sockets - are plain enums for the same reason, and so is `[compute_level_t](#compute_level_t)`, so a QoS rank never passes for a domain position.

See also: [memory_domain_index_t](#memory_domain_index_t), which indexes a different array and must not be confused with this.

Notes: The implicit widening also means a raw `array[compute_domain]` still compiles. The enums stop the argument-passing mistake, not the subscript one. Rust's newtypes stop both.

## memory_domain_index_t

A position in `[machine_topology](#machine_topology)'s` array of __memory__ domains, in [0, memory_domains_count).

```cpp
enum memory_domain_index_t
```

## compute_level_t

A rung of the dense QoS ladder, in [0, compute_levels_count), least performant first.

```cpp
enum compute_level_t
```

## thread_in_domain

One thread, situated in one compute domain - the "where I am" every callback receives.

```cpp
template <typename index_type_ = std::size_t>
struct thread_in_domain
```

Every dispatch hands its callback two things: the work, and this. A callback that only needs the thread can take the implicit conversion to a bare index; one placing memory reads `compute_domain` to find the node it runs on.

- `thread` `thread_index_t`: The thread or core index, in [0, threads_count).
- `compute_domain` `compute_domain_index_t`: The compute domain the thread is pinned to, in [0, compute_domains_count).

## ram_page_setting_t

One page size the kernel offers, and how many pages of it exist.

```cpp
struct ram_page_setting_t
```

A machine reports several: the base page every allocation uses by default, and whichever huge page sizes the hardware and kernel agree on. `available_pages` counts what was reserved, and `free_pages` what nobody took yet, letting an allocator tell "unsupported" from "exhausted".

- `bytes_per_page` `std::size_t`: Huge page size in bytes, e.g.
- `available_pages` `std::size_t`: Number of pages available for this size, 0 if not available.
- `free_pages` `std::size_t`: Number of pages available and unused, 0 if not available.

See also: [ram_capabilities](https://www.unum.cloud/docs/forkunion/cpp/quickstart.md#ram_capabilities)

## memory_domain

Describes a NUMA node, containing its ID, memory size, and core IDs.

```cpp
template <std::size_t max_page_sizes_ = 4>
struct memory_domain
```

- `max_page_sizes_k` `std::size_t`
- `memory_domain_id` `memory_domain_id_t`: The OS's id for this memory domain: a NUMA node number, in [0, numa_max_node()].
- `socket_id` `socket_id_t`: Physical CPU socket ID.
- `volume_ram` `std::size_t`: RAM volume in bytes.
- `core_ids` `std::span<core_id_t const>`: This node's logical cores, a slice of the topology's core array.
- `page_sizes` `ram_page_settings<max_page_sizes_k>`: Huge page sizes available on this memory domain.

See also: Views different slices of the `[machine_topology](#machine_topology)` structure.

## compute_domain_t

One bindable cluster of cores sharing a QoS class and locality - the compute axis.

```cpp
struct compute_domain_t
```

A compute domain is a contiguous run of same-capacity cores within a single NUMA node. It is what a pool binds to and the index a worker callback receives. Several compute domains may map to one memory domain - performance and efficiency cores sharing a memory controller - which is why compute and memory are separate axes rather than a single "colocation" cell.

- `memory_domain_id` `memory_domain_id_t`: The OS's id for the memory domain these cores live on.
- `memory_domain_index` `memory_domain_index_t`: Our dense index for that same memory domain, into `machine topology`'s array.
- `compute_level` `compute_level_t`: QoS ordinal, sorted least-to-most performant.
- `capacity` `std::size_t`: Relative throughput of __one__ core here; 0 when the platform exposes no rating.
- `cache_bytes_per_core` `std::size_t`: Bytes of the deepest cache confined to this domain, divided among the logical cores sharing one instance of it; 0 when unknown.
- `core_ids` `std::span<core_id_t const>`: This domain's logical cores, a slice of the topology's core array.

See also: [memory_domain](#memory_domain) is the memory axis; a `[machine_topology](#machine_topology)` exposes both plus their affinity.

## core_order_t

One core's place in its node's compute split, read by the harvest's sorts.

```cpp
struct core_order_t
```

- `capacity` `std::uint32_t`: Scheduler capacity; 0 where the kernel publishes none or rates every core alike.
- `class_max_frequency_khz` `std::uint32_t`: Highest `cpuinfo_max_freq` among its core PMU's cores, set only when capacities tie.
- `cluster_core_id` `core_id_t`: Lowest core of the cluster cache it sits under; -1 when its whole class is one cluster.
- `sibling_rank` `std::uint32_t`: 0 for a physical core's first hardware thread, 1 for its sibling.

## cache_instance_t

One instance of a cache: its bytes and the logical cores sharing it.

```cpp
struct cache_instance_t
```

- `bytes` `std::size_t`: 0 when no platform source names the cache.
- `sharing_cores` `std::uint32_t`: Logical cores listed in its `shared_cpu_list`, or CPUID's sharing width.
- `first_core_id` `core_id_t`: Lowest core sharing it; -1 where only CPUID answered.

## measured_edge_t

One recorded observation of a memory-fabric edge: what `initiator's` cores see when reaching memory resident on `target`.

```cpp
struct measured_edge_t
```

The chase and the stream are separate experiments, so each record carries its own experiment's metric and 0 for the other; `[measured_fabric](#measured_fabric)` folds the log into per-metric envelopes.

- `initiator` `compute_domain_index_t`: The compute domain whose cores issued the loads - the pool's pinning unit, never a memory domain: sibling domains on one controller still measure apart.
- `target` `memory_domain_index_t`: The memory domain whose DRAM answered the loads.
- `nanoseconds` `std::size_t`: Dependent-load latency of a lone core, in nanoseconds; 0 if unobserved.
- `megabytes_per_second` `std::size_t`: Saturated read bandwidth of all the initiator's cores at once, in MB/s; 0 if unobserved.

## chase_slot_t

One hop of the latency walk, padded to a cache line so consecutive slots never share one.

```cpp
struct chase_slot_t
```

- `next_slot_index` `std::uint32_t`: Index of the slot the walk visits next; no initializer, as [`resize_uninitialized`](https://www.unum.cloud/docs/forkunion/cpp/numa.md#resize_uninitialized) demands trivial construction and `thread_chase_list_` writes every slot anyway.

## logging_colors_t

Detects if the output stream supports ANSI color codes.

```cpp
struct logging_colors_t
```

Notes: Every accessor below returns its escape sequence when colors are on, and an empty string when they are off, so call sites interpolate them unconditionally.

## log_memory_volume_t

Formats memory volume in `bytes` with appropriate units and precision, like __"1.5 GB"__.

```cpp
struct log_memory_volume_t
```

## log_core_range_t

Formats a set of CPU core IDs in a compact and readable way, like __"0-3,5,7,8,10-12"__.

```cpp
struct log_core_range_t
```

## log_numa_topology_t

NUMA topology logger with compact tree design and color support.

```cpp
struct log_numa_topology_t
```

## core_mask

A dense bitset over `[core_id_t](#core_id_t):` the cores a thread may run on, or is confined to.

```cpp
template <typename allocator_type_ = std::allocator<core_mask_word_t>>
class core_mask
```

A machine is not the same thing as the slice of it we were handed. `taskset`, a cgroup `cpuset`, and a batch scheduler all narrow this set, and `hardware_concurrency` sees none of them. Sizing a pool from the machine and pinning to cores outside the set either escapes the restriction or, where the kernel enforces it, crowds every spinning worker onto the few cores left inside it.

Every platform hands out a dense core id, so one bitset covers them all: Linux and FreeBSD number logical processors from zero, and Windows packs `(group << 6) | bit` into one integer.

glibc's `__sched_cpualloc` rounds the count up and tail-calls `malloc`. A `[dynamic_array](https://www.unum.cloud/docs/forkunion/cpp/quickstart.md#dynamic_array)` sized from `possible_cores()` costs one cold-path allocation and, unlike a fixed `cpu_set_t`, never stops at glibc's 1024-core `CPU_SETSIZE`.

Notes: Not `CPU_ALLOC`, a `malloc` behind a name, which would both bypass this allocator and contradict what the library promises.

## core_mask

```cpp
core_mask() noexcept=default;
```

```cpp
core_mask(allocator_type_ const & allocator) noexcept;
```

## resize_for

Sizes the mask for `cores`, leaving it unusable rather than half-sized when the allocation fails.

```cpp
status_t resize_for(std::size_t const cores) noexcept;
```

## resize

Sizes the mask to hold every id this machine can produce, per [id_space](#id_space).

```cpp
status_t resize() noexcept;
```

## id_space

Upper bound on `[core_id_t](#core_id_t)` values this machine can produce, the width a mask covers.

```cpp
std::size_t id_space() noexcept;
```

Notes: Not `possible_cores()`; Windows ids are `(group << 6) | bit`, so a two-group machine with 80 processors still emits ids up to 103, and sizing by core count would drop them.

## measured_fabric

The measured memory fabric - what this process __observed__, as opposed to the structure the OS __declared__ in `[machine_topology](#machine_topology)`.

```cpp
template <typename allocator_type_ = std::allocator<char>>
class measured_fabric
```

Two query families: edge queries ____(initiator,target) describe one interconnect link; medium queries ____(target) describe the memory pool itself, independent of any initiator.

Completes the [`harvest`](#harvest) pipeline: a `[machine_topology](#machine_topology)` is harvested first and stays immutable, a pool spawns on it, and the fabric then harvests through that pool's workers, snapshotting what it needs so the topology may be freed after. [`harvest`](#harvest) is the only mutator and replaces the whole snapshot; before it, every query answers 0 and [`memory_levels_count`](#memory_levels_count) answers 1.

## measured_fabric

```cpp
constexpr measured_fabric() noexcept=default;
```

```cpp
measured_fabric(measured_fabric && o) noexcept;
```

```cpp
measured_fabric(measured_fabric const &)=delete;
```

## operator=

```cpp
measured_fabric & operator=(measured_fabric && other) noexcept;
```

```cpp
measured_fabric & operator=(measured_fabric const &)=delete;
```

## ~measured_fabric

```cpp
~measured_fabric() noexcept;
```

## *_domains_count

Snapshot of the topology's compute domain count at harvest time; 0 before any harvest.

```cpp
std::size_t compute_domains_count() const noexcept;
```

- `compute_domains_count`
- `memory_domains_count`

## memory_latency

Best dependent-load latency per edge, in nanoseconds; 0 if unwalked or out of range.

```cpp
std::size_t memory_latency(
    compute_domain_index_t const initiator,
    memory_domain_index_t const target) const noexcept;
```

Notes: The minimum across recordings, since interference only ever adds nanoseconds.

## memory_bandwidth

Best saturated read bandwidth per edge, in MB/s; 0 if unwalked or out of range.

```cpp
std::size_t memory_bandwidth(
    compute_domain_index_t const initiator,
    memory_domain_index_t const target) const noexcept;
```

Notes: The maximum across recordings, since interference only ever subtracts megabytes.

## memory_distance

Relative access distance on an edge, 10 = local per SLIT; 0 if out of range.

```cpp
std::size_t memory_distance(
    compute_domain_index_t const initiator,
    memory_domain_index_t const target) const noexcept;
```

The measured latency ratio to the initiator's local domain, times 10, rounded half-up and clamped to at least 10, so the local domain always carries the row's minimum. Unwalked edges fall back to 10-local / 20-remote; unclamped nanoseconds live in [`memory_latency`](#memory_latency).

## memory_level_in

The pool's derived speed class, 0 = fastest; keyed by the best bandwidth any initiator sustains to it, ties split by the best latency.

```cpp
std::size_t memory_level_in(
    memory_domain_index_t const memory_domain_index) const noexcept;
```

0 if out of range.

Notes: Tier boundaries are measurement-derived and can shift between harvests.

## memory_levels_count

Number of distinct derived memory tiers, at least 1.

```cpp
std::size_t memory_levels_count() const noexcept;
```

## edges_count

Number of recorded observations; each carries one experiment's metrics for one edge.

```cpp
std::size_t edges_count() const noexcept;
```

## edge_at

The observation at `index`, in [0, `edges_count()`).

```cpp
measured_edge_t const & edge_at(std::size_t const index) const noexcept;
```

## harvest

Harvests every reachable edge and the tiers derived from them through `pool's` workers, replacing any previous snapshot.

```cpp
template <typename pool_type_>
status_t harvest(
    machine_topology_t const & topology,
    pool_type_ & pool) noexcept;
```

The `topology` is only read.

Targets are the memory domains some worker can first-touch; __cpuless__ domains, like CXL expanders, stay unwalked, since portable first-touch cannot place pages there.

Notes: Not thread-safe: dispatches on the pool and rebuilds this fabric, so call it between task batches and do not query concurrently. Expect seconds of runtime on large fabrics.

Statuses: - [`config_mismatch_k`](https://www.unum.cloud/docs/forkunion/cpp/quickstart.md#config_mismatch_k) The pool cannot place pages on every domain it would walk, as a terminated pool or an unpinned one on a machine of several domains cannot.

## ram_page_settings

Describes the memory pages sizes configured & supported by the OS & CPU.

```cpp
template <std::size_t max_page_sizes_ = 4>
class ram_page_settings
```

This class avoids HugeTLBfs in favor of a direct access to the __/sys__ filesystem. Aside from fetching the stats, it also allows us to change settings if admin privileges are granted to running process.

#### Huge Pages and Transparent Huge Pages

Virtual Address Space, or VAS, is divided into pages, typically 4 KB in size. Converting a virtual address to a physical address requires a page table lookup. Think of it as a hash table... and as everyone knows, hash table lookups and updates aren't free, so most chips have a "Translation Lookaside Buffer" __TLB__ cache as part of the "Memory Management Unit" __MMU__ to speed up the process.

To keep it fast, in Big Data applications, one would like to use larger pages, to reduce the number of distinct entries in the TLB cache. Going from 4 KB to 2 MB or 1 GB "Huge Pages" __HPs__, reduces the table size by 512 or 262K times, respectively.

To benefit from those, some applications rely on "Transparent Huge Pages" __THP__, which are automatically allocated by the kernel. Such implicit behaviour isn't great for performance-oriented applications, so this inventory exposes __largest_free__, which the `[linux_numa_allocator](https://www.unum.cloud/docs/forkunion/cpp/numa.md#linux_numa_allocator)` consults before falling back to the base page size.

See also: [https://docs.kernel.org/admin-guide/mm/hugetlbpage.html](https://docs.kernel.org/admin-guide/mm/hugetlbpage.html)

## largest_free

Finds the largest Huge Pages size available for the given NUMA node.

```cpp
ram_page_setting_t largest_free() const noexcept;
```

## harvest

Fetches all available huge page sizes for the given NUMA node.

```cpp
status_t harvest(memory_domain_id_t memory_domain_id) noexcept;
```

Notes: Kernel support doesn't mean that pages of that size have a valid mount point.

## change

Attempts to reserve huge pages of a specific size on the current NUMA node.

```cpp
status_t change(
    memory_domain_id_t memory_domain_id,
    std::size_t page_size_bytes,
    std::size_t num_pages) noexcept;
```

- `memory_domain_id` (in): The NUMA node to reserve the pages on.
- `page_size_bytes` (in): The size of huge pages to reserve - must match an available size
- `num_pages` (in): Number of pages to reserve

Notes: Requires root privileges or appropriate capabilities

## machine_topology

NUMA topology descriptor: describing memory pools and core counts next to them.

```cpp
template <std::size_t max_page_sizes_ = 4,
          typename allocator_type_ = std::allocator<char>>
struct machine_topology
```

Uses dynamic memory to store the NUMA nodes and their cores. Assuming we may soon have Intel "Sierra Forest"-like CPUs with 288 cores with up to 8 sockets per node, this structure can easily grow to 10 KB.

## machine_topology

```cpp
constexpr machine_topology() noexcept=default;
```

```cpp
machine_topology(machine_topology && o) noexcept;
```

```cpp
machine_topology(machine_topology const &)=delete;
```

## operator=

```cpp
machine_topology & operator=(machine_topology && other) noexcept;
```

```cpp
machine_topology & operator=(machine_topology const &)=delete;
```

## ~machine_topology

```cpp
~machine_topology() noexcept;
```

## assign

Copy-assigns the topology from `other`.

```cpp
status_t assign(machine_topology const & other) noexcept;
```

Instead of a copy-constructor we expose an explicit operation that can fail - returning `false` if _any_ intermediate allocation fails. On success the current instance owns a deep copy, and on failure the current instance is unchanged.

- `other` (in): The topology to deep-copy from, left unchanged.

## memory_domains_count

Number of memory domains, one per NUMA node; see also [compute_domains_count](#compute_domains_count).

```cpp
std::size_t memory_domains_count() const noexcept;
```

## memory_domain_at

The memory domain at `memory_domain_index`, in [0, `memory_domains_count()`).

```cpp
memory_domain_t const & memory_domain_at(
    memory_domain_index_t const memory_domain_index) const noexcept;
```

## compute_domains_count

Number of compute domains - one per same-QoS core run within a node.

```cpp
std::size_t compute_domains_count() const noexcept;
```

## compute_levels_count

Number of distinct QoS classes across all compute domains, at least 1.

```cpp
std::size_t compute_levels_count() const noexcept;
```

## compute_domain_at

The compute domain at `compute_domain_index`, in [0, `compute_domains_count()`).

```cpp
compute_domain_t const & compute_domain_at(
    compute_domain_index_t const compute_domain_index) const noexcept;
```

## local_memory_of

The memory domain nearest a compute domain - its NUMA node; 0 if out of range.

```cpp
memory_domain_index_t local_memory_of(
    compute_domain_index_t const compute_domain_index) const noexcept;
```

## harvest_portable

Fills a single memory domain and compute domain covering every allowed core.

```cpp
status_t harvest_portable() noexcept;
```

The uniform view used when no richer topology source exists - a build without `FORKUNION_WITH_TOPOLOGY`, or a machine the kernel reports no NUMA for. Every query then returns a sensible whole-machine answer and a `distributed_pool` degenerates to one domain, so `[fu_topology_t](https://www.unum.cloud/docs/forkunion/c/topology.md#fu_topology_t)` is usable anywhere.

## harvest

Harvests CPU-memory topology - Linux NUMA nodes, or Apple Silicon performance levels.

```cpp
status_t harvest() noexcept;
```

Falls back to `try_harvest_portable` whenever no richer source is available, so a spawned pool always sees at least one compute and one memory domain.

## current_thread_id

The kernel's own identifier for the calling thread, or 0 where there is none.

```cpp
std::uint64_t current_thread_id() noexcept;
```

Linux calls it a `pid_t` and hands it out through `gettid`. Darwin has no `gettid` at all, and spells the same idea `pthread_threadid_np`, returning 64 bits. Both are the number a scheduler or a profiler will show you; neither is a `pthread_t`.

See also: pinned_thread_t::id, which caches it so other threads can read it.

## set_current_thread_name

Names the __calling__ thread, which is the only thread every platform lets us name.

```cpp
void set_current_thread_name(char const *thread_name) noexcept;
```

Linux's `pthread_setname_np` takes a thread and a name, so a spawner can name its workers. Apple's takes only a name and always renames the caller. Rather than branch on that at every call, the worker names itself once it is running - the one shape both kernels agree on.

## possible_cores

Upper bound on core IDs this machine may ever report, for sizing masks and names.

```cpp
std::size_t possible_cores() noexcept;
```

Not the same as `hardware_concurrency()` on Linux, where cores can be hot-plugged and the kernel reserves IDs for cores that are offline right now. Elsewhere the distinction does not exist.

## capture_thread_cores

Reads the cores the calling thread may run on into `cores`.

```cpp
status_t capture_thread_cores(core_mask_t & cores) noexcept;
```

## allowed_cores_count

Number of cores the calling thread may run on, or `possible_cores()` where unknowable.

```cpp
std::size_t allowed_cores_count() noexcept;
```

Notes: Prefer this to `std::thread::hardware_concurrency` when sizing a pool: the latter counts the machine's cores, not the ones this process was given.

## apply_thread_cores

Confines `thread` to the cores held by `cores`.

```cpp
status_t apply_thread_cores(
    native_thread_t thread,
    core_mask_t const & cores) noexcept;
```

The one place placement happens.

Linux hands out a `cpu_set_t` and honours it. FreeBSD spells the same idea `cpuset_t`. Windows addresses a core by processor group and bit, packed into each `[core_id_t](#core_id_t)`; a thread lives in exactly one group, so a mask spanning two is a caller error. Apple Silicon answers `KERN_NOT_SUPPORTED` to `thread_policy_set` - measured, not assumed - and offers only a Quality-of-Service class, chosen at creation. So a pool there partitions the __work__ by domain and lets the scheduler place the __threads__.

See also: try_pin_thread_to_cores, the adaptor that builds a mask from a core list.

## pin_thread_to_cores

Confines `thread` to the `count` cores listed in `cores`.

```cpp
status_t pin_thread_to_cores(
    native_thread_t,
    std::span<core_id_t const>) noexcept;
```

Notes: A thin adaptor: it builds a `[core_mask](#core_mask)` and defers to `try_apply_thread_cores`, which is where the per-platform placement lives. It owns no platform logic of its own.

## restore_thread_cores

Puts the calling thread back on the cores `saved` recorded before it was pinned.

```cpp
status_t restore_thread_cores(core_mask_t const & saved) noexcept;
```

A pool that narrows the caller owes it the mask it had, not the mask of the whole machine. The two differ under `taskset`, a cgroup `cpuset`, or any batch scheduler, and widening to the machine would hand the caller cores this process was never granted.

Note that no NUMA run-node policy is reset here: the pool never sets one. Memory placement goes through `mbind` on the allocation, not through the calling thread's policy, and `numa_run_on_node` would rewrite the very CPU mask we just restored.

Notes: A no-op where nothing was ever narrowed, or where `saved` was never captured.

## ram_page_size

Fetches the RAM page size in bytes.

```cpp
std::size_t ram_page_size() noexcept;
```

Notes: On Linux, this is the system page size, which may differ from Huge Pages sizes.

## volume_ram

Fetches the total RAM amount available on the system in bytes.

```cpp
std::size_t volume_ram() noexcept;
```

Notes: This function provides cross-platform detection of total physical memory.

## for_each_id_list_range

Hands every `[low, high]` range in a Linux id list - "0", "0-3", "0,2-4" - to `visit`.

```cpp
template <typename visitor_type_>
void for_each_id_list_range(
    char const *line,
    visitor_type_ && visit) noexcept;
```

Notes: One parser for every list the kernel publishes: `shared_cpu_list`, a domain's `cpulist`, and `node/online`. Ranges arrive inclusive on both ends, as written.

## cpu_list_within

Whether every core in a Linux cpulist line - "0", "0-3", "0,2-4" - belongs to `cores`.

```cpp
bool cpu_list_within(
    char const *line,
    std::span<core_id_t const> cores) noexcept;
```

Notes: Pass only complete lines: a truncated tail could name cores the verdict never saw.

## deepest_cache_of_core

The deepest data or unified cache serving `core_id`, confined to `domain_cores`.

```cpp
cache_instance_t deepest_cache_of_core(
    core_id_t core_id,
    std::span<core_id_t const> domain_cores) noexcept;
```

Two exact sources, no measurement: Linux's per-core cacheinfo sysfs, counting a level only if its `shared_cpu_list` stays within the domain - a socket-wide L3 is not one QoS class's to claim; elsewhere x86 CPUID leaf 0x4 / 0x8000001D, with the sharing width standing in for that containment. Arm has no userspace cache-geometry registers - `CCSIDR_EL1` is EL1-only - so sysfs is the only Arm source and other Arm hosts honestly report 0.

## cache_line_bytes

Bytes in one cache line - what a single access transfers.

```cpp
std::size_t cache_line_bytes() noexcept;
```

Exact sources only, no measurement: Linux's `coherency_line_size` - the only source on Arm, where `CCSIDR_EL1` is EL1-only - Apple's `hw.cachelinesize`, Windows' `CACHE_RELATIONSHIP`, and x86 CPUID leaf 0x4, whose line field [`deepest_cache_of_core`](#deepest_cache_of_core) above already decodes for its size arithmetic. Every source is gated to a power of two in [16, 1024], so a bogus reading falls through to the caller's compiled default rather than sizing an arena.

## destructive_interference_bytes

Bytes two cores can invalidate for each other - what padding must span.

```cpp
std::size_t destructive_interference_bytes() noexcept;
```

Not the same question as `cache_line_bytes:` x86's adjacent-line prefetcher pulls lines in pairs, so two 64-byte lines behave as one 128-byte unit of coherence traffic and padding must cover both. Everywhere else the line is the unit. This is the quantity [`default_alignment_k`](https://www.unum.cloud/docs/forkunion/cpp/quickstart.md#default_alignment_k) guesses from the target when no probe can run.

## unreachable_position_k

Marks a fabric position the pool has no worker on - cpuless, or beyond a partial spawn.

```cpp
std::size_t unreachable_position_k;
```

