# Topology

> ForkUnion for Zig

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.

`const forkunion = @import("forkunion");`

`const Topology = @import("forkunion").Topology;`

`const ComputeDomain = @import("forkunion").ComputeDomain;`

`const MemoryDomain = @import("forkunion").MemoryDomain;`

`const MemoryDomainId = @import("forkunion").MemoryDomainId;`

`const Fabric = @import("forkunion").Fabric;`

## ThreadInDomain

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

```zig
pub const ThreadInDomain = struct
```

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.
- `compute_domain` `ComputeDomain`: The compute domain the thread is pinned to - a same-QoS core cluster.

## Topology

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

```zig
pub const Topology = struct
```

Build one with [`init`](https://www.unum.cloud/docs/forkunion/zig/numa.md#init), query it through the methods below, and hand it to pool constructors and
memory-domain allocators so they know which machine they are placing work and pages onto.

- `handle` `*anyopaque`

## init

Discovers this machine's topology, returning an owned handle.

```zig
pub fn init() Error!Topology
```

## deinit

Releases the topology handle.

```zig
pub fn deinit(self: Topology) void
```

## logicalCoresCount

Returns the number of logical CPU cores available

```zig
pub fn logicalCoresCount(self: Topology) Error!usize
```

## memoryDomainsCount

Returns the number of memory domains available

```zig
pub fn memoryDomainsCount(self: Topology) Error!usize
```

## memoryDomainIdAtIndex

Resolves a memory domain's dense index to the allocator's OS id; `.none` if out of range.

```zig
pub fn memoryDomainIdAtIndex(
    self: Topology,
    memory_domain: MemoryDomain,
) Error!MemoryDomainId
```

A
real domain may itself carry `.none` where the OS names none, so an index this machine does
not have is a reported refusal rather than that same value.

## computeDomainsCount

Returns the number of distinct thread compute_domains

```zig
pub fn computeDomainsCount(self: Topology) Error!usize
```

## logicalCoresCountIn

Returns the number of logical cores backing a given compute domain (0 if out of range).

```zig
pub fn logicalCoresCountIn(
    self: Topology,
    compute_domain: ComputeDomain,
) Error!usize
```

## computeLevelIn

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

```zig
pub fn computeLevelIn(
    self: Topology,
    compute_domain: ComputeDomain,
) Error!usize
```

## localMemoryOf

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

```zig
pub fn localMemoryOf(
    self: Topology,
    compute_domain: ComputeDomain,
) Error!MemoryDomain
```

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

## computeLevelsCount

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

```zig
pub fn computeLevelsCount(self: Topology) Error!usize
```

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

## computeCapacityIn

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

```zig
pub fn computeCapacityIn(
    self: Topology,
    compute_domain: ComputeDomain,
) Error!usize
```

A magnitude on the Linux `cpu_capacity` scale, where 1024 is the fastest core present.
Weight work by this - [`computeLevelIn`](#computeLevelIn) is a dense ordinal and must never be divided by.
Platforms that rank cores without rating them report 0; weigh by core count instead.

## computeCacheBytesPerCoreIn

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

```zig
pub fn computeCacheBytesPerCoreIn(
    self: Topology,
    compute_domain: ComputeDomain,
) Error!usize
```

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

## volumeRam

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

```zig
pub fn volumeRam(self: Topology) Error!usize
```

## volumeRamIn

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

```zig
pub fn volumeRamIn(
    self: Topology,
    memory_domain: MemoryDomain,
) Error!usize
```

## volumeHugePages

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

```zig
pub fn volumeHugePages(self: Topology) Error!usize
```

## volumeHugePagesIn

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

```zig
pub fn volumeHugePagesIn(
    self: Topology,
    memory_domain: MemoryDomain,
) Error!usize
```

## hugePagesCount

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

```zig
pub fn hugePagesCount(self: Topology) Error!usize
```

## hugePagesCountIn

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

```zig
pub fn hugePagesCountIn(
    self: Topology,
    memory_domain: MemoryDomain,
) Error!usize
```

## ComputeDomain

A dense compute-domain index, in `0..Topology.computeDomainsCount()`.

```zig
pub const ComputeDomain = enum(usize)
```

A distinct type from [`MemoryDomain`](#MemoryDomain) because the two index different axes of the machine and the
compiler is the only thing that can tell them apart at a call site.

## at

Wraps a dense index, as produced by iterating `0..computeDomainsCount()`.

```zig
pub fn at(dense_index: usize) ComputeDomain
```

## *

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

```zig
pub fn index(self: ComputeDomain) usize
```

## MemoryDomain

A dense memory-domain index, in `0..Topology.memoryDomainsCount()`.

```zig
pub const MemoryDomain = enum(usize)
```

## at

Wraps a dense index, as produced by iterating `0..memoryDomainsCount()`.

```zig
pub fn at(dense_index: usize) MemoryDomain
```

## MemoryDomainId

The sparse OS id the kernel labels a NUMA node with, which the allocators key off.

```zig
pub const MemoryDomainId = enum(i32)
```

Distinct from [`MemoryDomain`](#MemoryDomain): that one iterates, this one allocates. An [`AllocationResult`](https://www.unum.cloud/docs/forkunion/zig/numa.md#AllocationResult)
carries only this id, so it can outlive the [`Topology`](#Topology) that resolved it.

- `none` = `-1`: Names no domain - what an out-of-range lookup answers.

## identifier

The raw OS id, for the FFI boundary.

```zig
pub fn identifier(self: MemoryDomainId) i32
```

## isValid

Whether this id names a real domain rather than the [`none`](#none) sentinel.

```zig
pub fn isValid(self: MemoryDomainId) bool
```

## Fabric

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

```zig
pub const Fabric = struct
```

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, a [`Pool`](https://www.unum.cloud/docs/forkunion/zig/quickstart.md#Pool)
spawns on it, and the fabric then harvests through that pool's pinned workers, snapshotting what
it needs so the topology may be freed after. Before a harvest every query answers 0, and
[`memoryLevelsCount`](#memoryLevelsCount) answers 1.

- `handle` `*anyopaque`

## init

Creates an empty, unharvested fabric.

```zig
pub fn init() Error!Fabric
```

## deinit

Destroys the fabric and frees its observations.

```zig
pub fn deinit(self: Fabric) void
```

## harvest

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

```zig
pub fn harvest(
    self: Fabric,
    topo: Topology,
    pool: Pool,
) Error!void
```

Returns `false` on allocation failure, or for a pool whose workers are not pinned per
domain: flat pools and those pinned to one compute domain have no fabric to walk; it is left
empty, never half-written. Not thread-safe: it dispatches on the pool and rebuilds the
fabric, so call it between task batches. Expect seconds of runtime on large fabrics.

## memoryLatency

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.

```zig
pub fn memoryLatency(
    self: Fabric,
    compute_domain: ComputeDomain,
    memory_domain: MemoryDomain,
) Error!usize
```

## memoryBandwidth

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.

```zig
pub fn memoryBandwidth(
    self: Fabric,
    compute_domain: ComputeDomain,
    memory_domain: MemoryDomain,
) Error!usize
```

## memoryDistance

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.

```zig
pub fn memoryDistance(
    self: Fabric,
    compute_domain: ComputeDomain,
    memory_domain: MemoryDomain,
) Error!usize
```

## memoryLevelIn

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.

```zig
pub fn memoryLevelIn(
    self: Fabric,
    memory_domain: MemoryDomain,
) Error!usize
```

## memoryLevelsCount

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

```zig
pub fn memoryLevelsCount(self: Fabric) Error!usize
```

