# Topology

> ForkUnion for Rust

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.

`use forkunion::Fabric;`

`use forkunion::ComputeDomain;`

`use forkunion::MemoryDomain;`

`use forkunion::ComputeLevel;`

`use forkunion::MemoryDomainId;`

`use forkunion::Topology;`

`use forkunion::ThreadInDomain;`

## Fabric

The measured memory fabric - what this process observed, as opposed to the structure a [`Topology`](#Topology) declares.

```rust
pub struct Fabric
```

Two query families: edge queries `(initiator, target)` describe one
interconnect link; medium queries `(target)` describe the memory pool itself, with no initiator.

Completes the [`harvest`](#harvest) pipeline: a [`Topology`](#Topology) is harvested first and stays immutable and
shareable, a [`ThreadPool`](https://www.unum.cloud/docs/forkunion/rust/quickstart.md#ThreadPool) spawns on it, and the fabric then harvests through that pool's
pinned workers, snapshotting what it needs so the topology may be dropped after.
Before a harvest every query answers 0, and [`memory_levels_count`](#memory_levels_count)
answers 1.

Examples: ```
use forkunion::*;
let topology = Topology::new().unwrap();
let mut pool = spawn(&#x26;topology, 4);
let mut fabric = Fabric::new().unwrap();
if fabric.harvest(&#x26;topology, &#x26;mut pool).is_ok() {
    let local = topology.local_memory_of(ComputeDomain(0)).unwrap();
    assert!(fabric.memory_latency(ComputeDomain(0), local).unwrap() > 0);
}
```

Implements: `Send`, `Sync`, `Drop`

## new

Creates an empty, unharvested fabric.

```rust
pub fn new() -> Result<Fabric>
```

## harvest

Measures the memory fabric through the pool's pinned workers, replacing any previous harvest; the `topology` is only read.

```rust
pub fn harvest(&mut self, topology: &Topology, pool: &mut ThreadPool) -> Result<()>
```

Returns `false` on allocation failure, or for a pool with workers not pinned per domain -
one restricted below [`Capabilities::PLACE_MEMORY_ON_DOMAIN`](https://www.unum.cloud/docs/forkunion/rust/quickstart.md#PLACE_MEMORY_ON_DOMAIN) or pinned via
[`ThreadPool::spawn_on`](https://www.unum.cloud/docs/forkunion/rust/quickstart.md#spawn_on); the fabric is then left empty, never half-written. Not
thread-safe: it dispatches on the pool and rebuilds `self`, so call it between task batches.
Expect seconds of runtime on large fabrics.

## memory_latency

Returns the measured dependent-load latency, in nanoseconds, on an edge - the best recording; 0 before a harvest, for an edge no worker could reach, or an out-of-range index.

```rust
pub fn memory_latency(
    &self,
    compute_domain: ComputeDomain,
    memory_domain: MemoryDomain,
) -> Result<usize>
```

## memory_bandwidth

Returns the measured saturated read bandwidth (MB/s) on an edge, streamed by all the initiator domain's workers at once - the best recording; 0 if unreached or out of range.

```rust
pub fn memory_bandwidth(
    &self,
    compute_domain: ComputeDomain,
    memory_domain: MemoryDomain,
) -> Result<usize>
```

## memory_distance

Returns the relative access distance on an edge (10 = local, per the SLIT convention): the measured latency ratio to the initiator's local domain, clamped so local carries the row's minimum; unwalked edges fall back to 10-local / 20-remote.

```rust
pub fn memory_distance(
    &self,
    compute_domain: ComputeDomain,
    memory_domain: MemoryDomain,
) -> Result<usize>
```

## memory_level_in

Returns the derived speed class of a memory domain (lower = faster: HBM < DDR < CXL), keyed by the best bandwidth any initiator sustains to it, ties split by the best latency.

```rust
pub fn memory_level_in(&self, memory_domain: MemoryDomain) -> Result<usize>
```

## memory_levels_count

Returns the number of distinct derived memory tiers, the memory-axis twin of [`Topology::compute_levels_count`](#compute_levels_count); 1 on single-tier systems and before a harvest.

```rust
pub fn memory_levels_count(&self) -> usize
```

## ComputeDomain

A position in the topology's array of __compute__ domains, in `[0, compute_domains_count())`.

```rust
pub struct ComputeDomain(pub usize);
```

Distinct from [`MemoryDomain`](#MemoryDomain), and deliberately not interchangeable with it. The two axes are
indexed independently: an Apple M5 Pro reports three compute domains over a single memory
domain, so a compute index of `2` names no memory domain at all.

This is not hypothetical. Handing a compute-domain index to [`DomainAllocator::new`](https://www.unum.cloud/docs/forkunion/rust/parallel-iterators.md#new), which
expects a memory-domain index, compiles and works on every machine where the two counts happen
to match, then misplaces memory where they diverge. Unlike a C++ `enum`, a newtype also refuses
`slice[compute_domain]`, because `Index<usize>` will not accept it - which is the other half of
the same bug.

- `0` `usize`

Implements: `Copy`, `Clone`, `Debug`, `StructuralPartialEq`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash`

## *

The raw index, for the FFI boundary and for arithmetic.

```rust
pub fn get(self) -> usize
```

## MemoryDomain

A position in the topology's array of __memory__ domains, in `[0, memory_domains_count())`, kept separate from [[`ComputeDomain`](#ComputeDomain)] for the reasons given there.

```rust
pub struct MemoryDomain(pub usize);
```

- `0` `usize`

Implements: `Copy`, `Clone`, `Debug`, `StructuralPartialEq`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash`

## ComputeLevel

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

```rust
pub struct ComputeLevel(pub usize);
```

An ordinal for grouping, never a magnitude to weigh work by, and never a domain position -
so it is neither a bare `usize` nor interchangeable with [`ComputeDomain`](#ComputeDomain).

- `0` `usize`

Implements: `Copy`, `Clone`, `Debug`, `StructuralPartialEq`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash`

## get

The raw rung, for the FFI boundary and for indexing a per-level table.

```rust
pub fn get(self) -> usize
```

## MemoryDomainId

An OS memory-domain ID - a NUMA node - the allocators key off, obtained from [`Topology::memory_domain_id_at_index`](#memory_domain_id_at_index); `-1` when there is none.

```rust
pub struct MemoryDomainId(pub i32);
```

Distinct from [[`MemoryDomain`](#MemoryDomain)]: that is a dense index for iteration, this is the sparse OS ID
the kernel labels a node with. A [`DomainAllocator`](https://www.unum.cloud/docs/forkunion/rust/numa.md#DomainAllocator) holds only this
ID, so it - and every [`AllocationResult`](https://www.unum.cloud/docs/forkunion/rust/numa.md#AllocationResult) it hands out - is free of
the topology handle and can outlive it.

- `0` `i32`

Implements: `Copy`, `Clone`, `Debug`, `StructuralPartialEq`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash`

## get

The raw OS ID, for the FFI boundary; `-1` names no domain.

```rust
pub fn get(self) -> i32
```

## is_valid

Whether this ID names a real domain rather than the `-1` sentinel.

```rust
pub fn is_valid(self) -> bool
```

## Topology

An explicit, owned handle to the machine's compute and memory topology.

```rust
pub struct Topology
```

The topology is discovered once, when [`Topology::new`](https://www.unum.cloud/docs/forkunion/rust/parallel-iterators.md#new) is called, and every affinity query,
NUMA allocation, and pool spawn is answered against this handle rather than a process-wide
singleton. Create one near the start of a program and share it (`&Topology`) with the pools and
allocators that need it; it is `Send` + `Sync`, so a single handle serves every thread.

Examples: ```
use forkunion::*;

let topology = Topology::new().expect("Failed to probe topology");
let cores = topology.logical_cores_count().unwrap();
let pool = ThreadPool::spawn(&#x26;topology, cores.max(1)).expect("Failed to spawn pool");
assert_eq!(pool.threads_count(), cores.max(1));
```

Implements: `Send`, `Sync`, `Drop`

## new

Probes the machine and builds a fresh topology handle.

```rust
pub fn new() -> Result<Self>
```

Errors: Returns [`Error`](https://www.unum.cloud/docs/forkunion/rust/quickstart.md#Error) if the C side could not allocate or populate the handle.

## logical_cores_count_in

Returns the number of logical cores backing a given compute domain.

```rust
pub fn logical_cores_count_in(&self, compute_domain: ComputeDomain) -> Result<usize>
```

Zero if `compute_domain` is out of range. Use it to size a per-compute-domain pool
([`ThreadPool::spawn_on`](https://www.unum.cloud/docs/forkunion/rust/quickstart.md#spawn_on)) or to weight work across uneven
compute domains.

## logical_cores_count

Returns the number of logical CPU cores available on the system.

```rust
pub fn logical_cores_count(&self) -> Result<usize>
```

## compute_domains_count

Returns the number of distinct thread compute_domains available.

```rust
pub fn compute_domains_count(&self) -> Result<usize>
```

A "compute_domain" represents a group of threads that share the same:

- __NUMA memory domain__ - threads with fast local memory access

- __Quality-of-Service level__ - P-cores vs E-cores on heterogeneous CPUs

- __Cache hierarchy__ - threads sharing L3 cache

#### Typical Values

- `1` on most desktop, laptop, or IoT platforms with unified memory

- `2-8` on typical dual-socket servers or heterogeneous mobile chips

- `4-32` on high-end cloud servers with multiple sockets

## compute_level_in

Returns the performance level of a compute domain (higher = more performant).

```rust
pub fn compute_level_in(&self, compute_domain: ComputeDomain) -> Result<ComputeLevel>
```

## compute_levels_count

Returns the number of distinct Quality-of-Service levels.

```rust
pub fn compute_levels_count(&self) -> Result<usize>
```

May be smaller than [`compute_domains_count`](#compute_domains_count), as several
domains can share one level - equally-fast cores may still be split across cache clusters,
or across NUMA nodes.

## compute_capacity_in

Returns the relative throughput of one core in a compute domain (0 if unknown).

```rust
pub fn compute_capacity_in(&self, compute_domain: ComputeDomain) -> Result<usize>
```

A magnitude on the Linux `cpu_capacity` scale, where 1024 is the fastest core present.
This is the number to weight work by - [`compute_level_in`](#compute_level_in) is a
dense ordinal and must never be divided by. Platforms that rank cores without rating them
report 0 here; fall back to [`threads_count_in`](https://www.unum.cloud/docs/forkunion/rust/quickstart.md#threads_count_in) when
they do.

## compute_cache_bytes_per_core_in

Returns each core's share of the deepest cache confined to a compute domain (0 if unknown).

```rust
pub fn compute_cache_bytes_per_core_in(&self, compute_domain: ComputeDomain) -> Result<usize>
```

Sizes a cache-resident chunk, which is a different question from how many chunks a domain
deserves - domains of equal throughput may back onto very differently sized caches.

## memory_domains_count

Returns the number of memory domains available.

```rust
pub fn memory_domains_count(&self) -> Result<usize>
```

## memory_domain_id_at_index

Resolves a memory domain's dense index to the OS ID a [`DomainAllocator`](https://www.unum.cloud/docs/forkunion/rust/numa.md#DomainAllocator) takes.

```rust
pub fn memory_domain_id_at_index(&self, memory_domain: MemoryDomain) -> Result<MemoryDomainId>
```

A real domain may itself carry `-1` where the OS names none, so an out-of-range index is a
reported failure rather than the same sentinel.

## local_memory_of

Returns the memory domain nearest a given compute domain, its local allocation target.

```rust
pub fn local_memory_of(&self, compute_domain: ComputeDomain) -> Result<MemoryDomain>
```

Performance - tiers, latencies, bandwidths, distances - is not the topology's to declare:
harvest a [`Fabric`](#Fabric) to measure it in-process.

## volume_ram_in

Returns the RAM volume, in bytes, held by a given memory domain (0 if out of range).

```rust
pub fn volume_ram_in(&self, memory_domain: MemoryDomain) -> Result<usize>
```

## volume_ram

Returns the total RAM volume, in bytes, across all memory domains, regardless of page size.

```rust
pub fn volume_ram(&self) -> Result<usize>
```

## volume_huge_pages_in

Returns the huge-page volume, in bytes, on a given memory domain (0 if out of range).

```rust
pub fn volume_huge_pages_in(&self, memory_domain: MemoryDomain) -> Result<usize>
```

## volume_huge_pages

Returns the total huge-page volume, in bytes, across all memory domains.

```rust
pub fn volume_huge_pages(&self) -> Result<usize>
```

## huge_pages_count_in

Returns the number of free huge pages in a given memory domain (0 if out of range).

```rust
pub fn huge_pages_count_in(&self, memory_domain: MemoryDomain) -> Result<usize>
```

## huge_pages_count

Returns the total number of free huge pages across all memory domains.

```rust
pub fn huge_pages_count(&self) -> Result<usize>
```

## ThreadInDomain

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

```rust
pub struct ThreadInDomain
```

Every dispatch hands its callback two things: the work, and this. A callback placing memory
reads `compute_domain` to find the node it runs on; one indexing per-thread scratch reads
`thread`.

- `thread` `usize`: The physical thread executing the work (0-based).
- `compute_domain` `usize`: The compute domain the thread is pinned to - a same-QoS core cluster within a memory domain.

Implements: `Copy`, `Clone`, `Debug`, `StructuralPartialEq`, `PartialEq`, `Eq`

