Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
ddf8ea0
Fix: Safeguard `try` for `std::thread`
ashvardanian Oct 9, 2025
0ec9ae9
Docs: Why no Miri or Loom?
ashvardanian Oct 9, 2025
916e641
Add: Draft Zig binding
ashvardanian Oct 10, 2025
d9455c8
Improve: Thread name formatting
ashvardanian Oct 10, 2025
fb063a7
Fix: Use aligned allocs for `opaque_pool_t`
ashvardanian Oct 11, 2025
0b9ed05
Improve: Handle `void` contexts
ashvardanian Oct 11, 2025
6d6283a
Make: Upgrade Zig to v0.16
ashvardanian Oct 11, 2025
bf998f9
Add: Bench against libXEV and Spice
ashvardanian Oct 11, 2025
ca3d42b
Improve: Static slicing for `std.Thread.Pool`
ashvardanian Oct 11, 2025
e8c6b48
Improve: Test C stable API
ashvardanian Oct 11, 2025
a21ac35
Make: Skip C11 tests on MSVC - lacking atomics
ashvardanian Oct 11, 2025
1e9540f
Make: Apple's `libSystem` contains Blocks runtime
ashvardanian Oct 11, 2025
18b6d63
Improve: Add `worker_yield_t` hook for idle backoff
guillaumeriousat Oct 8, 2025
101a955
Fix: Route `standard_worker_yield` through `micro_yield()`
guillaumeriousat Oct 8, 2025
53e1b5d
Improve: Select worker-yield overloads with `if constexpr`
guillaumeriousat Oct 9, 2025
541842c
Improve: Shared `call_yield_` with & w/out thread
ashvardanian Oct 11, 2025
ac41200
Merge branch 'pr/feat-worker-yield' into main-dev
ashvardanian Oct 11, 2025
86b95aa
Merge branch 'zig' into main-dev
ashvardanian Oct 11, 2025
bbb9e90
Add: NUMA-aware allocator for Zig
ashvardanian Oct 11, 2025
0167211
Make: Backport to Zig 0.15
ashvardanian Oct 11, 2025
a707d55
Fix: Deprecate `spice` comparison
ashvardanian Oct 11, 2025
9982ad9
Improve: `RoundRobinVec` code nesting
ashvardanian Oct 18, 2025
46bb382
Improve: Validate `CacheAligned` padding
ashvardanian Oct 18, 2025
3bf34e9
Fix: Over-aligned `AllocationResult`
ashvardanian Oct 19, 2025
e7932c4
Add: Reductions in Rust
ashvardanian Oct 19, 2025
cb6b257
Add: Stoppable & Exception-handling `try_for`
ashvardanian Oct 19, 2025
0911619
Break: Name spacing/capitalization
ashvardanian Oct 19, 2025
9a08a62
Make: Bump CI
ashvardanian Oct 22, 2025
6da19ed
Break: New project name & link
ashvardanian Mar 16, 2026
7de0186
Merge: WFET patches
ashvardanian Mar 25, 2026
1545311
Break: Generation tokens for non-blocking multi-pool coordination
ashvardanian Jul 9, 2026
0c92de8
Merge: v3 redesign & hardware checks
ashvardanian Jul 9, 2026
39b007c
Fix: Rust examples for by-reference `for_threads`
ashvardanian Jul 9, 2026
d3a2a5a
Chore: Drop decorative comment banners
ashvardanian Jul 9, 2026
d230ab1
Make: Bump CI runners and add Arm64 + sanitizer coverage
ashvardanian Jul 9, 2026
64d4bdf
Break: Split topology into compute and memory domains
ashvardanian Jul 9, 2026
f12d0ef
Docs: Reword prong metadata for two-axis topology
ashvardanian Jul 9, 2026
c27faf8
Add: Memory tiers, cpuless domains, HMAT metrics, and macOS harvest
ashvardanian Jul 9, 2026
d8e170f
Make: Rename allocator numa_node to memory_domain
ashvardanian Jul 9, 2026
b71341f
Add: Rust scope() API replacing the SafePtr smuggle
ashvardanian Jul 9, 2026
6c7f62d
Add: Miri coverage of the pure-Rust unsafe
ashvardanian Jul 9, 2026
0272b39
Docs: Reword stale topology phrasing to compute/memory domains
ashvardanian Jul 9, 2026
3ebeea5
Fix: NUMA topology harvesting on multi-socket hosts
ashvardanian Jul 9, 2026
95df7bc
Improve: Compute-domain metadata across the bindings
ashvardanian Jul 9, 2026
384bef5
Break: Sound, allocation-free `RoundRobinVec` bulk operations
ashvardanian Jul 9, 2026
8350177
Improve: NUMA backends for the Rust N-body, and dual-socket results
ashvardanian Jul 9, 2026
82ee57d
Chore: Drop the last decorative rule lines
ashvardanian Jul 9, 2026
5ea9298
Improve: Private claim cursors for `for_n_dynamic`
ashvardanian Jul 9, 2026
1cbd17f
Merge: Multi-socket harvesting & sound Rust bulk ops
ashvardanian Jul 9, 2026
c5f30cd
Fix: Seed where a stealing thread starts probing, not just its stride
ashvardanian Jul 9, 2026
ec013e8
Break: Layer capability macros, split the umbrella header
ashvardanian Jul 10, 2026
0020411
Make: Expose each capability as an AUTO/ON/OFF tri-state
ashvardanian Jul 10, 2026
818151a
Break: Remove every back-compatibility shim
ashvardanian Jul 10, 2026
93a92b9
Chore: One documentation convention, enforced by hooks
ashvardanian Jul 10, 2026
2ae8150
Add: Windows support for topology, NUMA pools, and pinning
ashvardanian Jul 10, 2026
33497ab
Fix: Size and pin pools from the cores we were given
ashvardanian Jul 10, 2026
586e138
Add: Owning arrays, and rebuild the topology on them
ashvardanian Jul 10, 2026
fa78301
Break: One vocabulary, one canonical order, across every surface
ashvardanian Jul 10, 2026
f7f53bb
Chore: Name detection macros consistently
ashvardanian Jul 10, 2026
d7774e5
Add: Monitored waits, per-loop wait bounds, and Zawrs
ashvardanian Jul 10, 2026
6d29828
Fix: Restore Windows NUMA allocation and page-size probe
ashvardanian Jul 10, 2026
c2e9669
Improve: Cleaner feature controls, one set across the SDKs
ashvardanian Jul 10, 2026
90546ed
Improve: Unified capability enumeration, `pool_kind_t`, leaner ABI
ashvardanian Jul 11, 2026
6903711
Break: Explicit `fu_topology_t` handle across the C ABI, no singletons
ashvardanian Jul 11, 2026
06bf8fe
Break: DomainAllocator, symmetric containers, SDK benchmark alignment
ashvardanian Jul 13, 2026
12bab43
Improve: Full backend matrix via one compile-time-policy dispatch per…
ashvardanian Jul 13, 2026
05ed868
Fix: Assemble the WFET capability probe on stock toolchains
ashvardanian Jul 13, 2026
3833a67
Fix: Build clean under -Wall -Wextra -Werror
ashvardanian Jul 13, 2026
d171746
Make: Expand CI to validate cross-platform compatibility
ashvardanian Jul 13, 2026
7560030
Chore: Split Rust and Zig bindings into per-header modules
ashvardanian Jul 13, 2026
e85d35f
Fix: Harden pool-completion tests against scheduler starvation
ashvardanian Jul 13, 2026
a960fa3
Improve: Reach the CPU yield intrinsics on MSVC
ashvardanian Jul 13, 2026
1da97c7
Fix: Assemble the RISC-V pause hint on stock toolchains
ashvardanian Jul 13, 2026
7a4fd4e
Improve: Flatten the libc and hwprobe capability detection
ashvardanian Jul 13, 2026
b903e50
Improve: Model Android and FreeBSD affinity and scheduling
ashvardanian Jul 13, 2026
5f26aba
Make: Build the old-glibc floor on manylinux_2_28
ashvardanian Jul 13, 2026
315da41
Fix: Cross-platform CI test failures
ashvardanian Jul 13, 2026
ef3d11c
Make: Static-link the qemu-run cross-compile test binaries
ashvardanian Jul 14, 2026
c427d6c
Improve: Locate and value-print test failures, backtrace on crash
ashvardanian Jul 14, 2026
239e3b0
Fix: Robust NUMA placement under sandboxed and single-node hosts
ashvardanian Jul 14, 2026
f30ce60
Fix: Route platform checks through the FU_* vocabulary
ashvardanian Jul 14, 2026
c7be3fb
Improve: Steer Apple efficiency-core pools via QoS
ashvardanian Jul 14, 2026
e351fd1
Make: Refresh retired CI runners, fix FreeBSD linkage
ashvardanian Jul 14, 2026
4ce5b40
Make: Bump Zig to 0.16 for macOS 26
ashvardanian Jul 14, 2026
4145073
Add: Cache-line demote & promote hints for x86, Arm, and RISC-V
ashvardanian Jul 14, 2026
ea5399a
Improve: Pools carry a cache-hints policy beside the yield
ashvardanian Jul 14, 2026
c525460
Improve: Private cursors, read-probes, and demotions in dynamic stealing
ashvardanian Jul 14, 2026
134b7f6
Improve: Dispatch curated yield-and-hints pool variants at runtime
ashvardanian Jul 14, 2026
50d39e2
Improve: Flatten the distributed pool's colocations into one array
ashvardanian Jul 14, 2026
11aa660
Fix: Hoist dead work and stale docs out of the pool internals
ashvardanian Jul 14, 2026
ef68bca
Make: Align benchmark builds and add Taskflow as a baseline
ashvardanian Jul 14, 2026
2604198
Add: Label-propagation benchmark replacing triangle counting
ashvardanian Jul 14, 2026
198b04f
Make: Install git in the FreeBSD CI VM for Taskflow fetch
ashvardanian Jul 14, 2026
6a84509
Add: FreeBSD reschedule, topology, memory placement, and superpages
ashvardanian Jul 14, 2026
45eb844
Improve: Time-budgeted benchmarks, symmetric across C++, Rust, and Zig
ashvardanian Jul 14, 2026
6b77064
Improve: Deepen test coverage across schedulers, lifecycles, and the …
ashvardanian Jul 14, 2026
02bb703
Improve: Normalize the N-body kernels across languages
ashvardanian Jul 14, 2026
feeaddd
Improve: Flatten test bodies into helpers and split bundled concerns
ashvardanian Jul 14, 2026
e3d7ff2
Make: Restructure the pre-release CI around matrices and ctest
ashvardanian Jul 14, 2026
c2eb3a7
Fix: Probe FreeBSD's memory placement at runtime, not just compile time
ashvardanian Jul 14, 2026
a52dfef
Make: Rewrite the pre-commit hook single-pass and Windows-proof
ashvardanian Jul 14, 2026
e3396ec
Docs: Refresh the README against the v3 APIs and slim the benchmarks
ashvardanian Jul 14, 2026
1be2492
Make: Green the CI matrix - apt sources, tar, shellcheck, cross rows
ashvardanian Jul 14, 2026
0891489
Fix: Build the timed-wait monitors under -march=native
ashvardanian Jul 14, 2026
4e87654
Fix: Sync build-system references to place-* feature names
ashvardanian Jul 15, 2026
c2ee9d5
Chore: Add dprint/taplo config-file formatters
ashvardanian Jul 15, 2026
45e4753
Docs: Refresh the intro and complete the v3 API coverage
ashvardanian Jul 15, 2026
c322c93
Add: Enumerate per-domain cache sizes from sysfs and CPUID
ashvardanian Jul 15, 2026
3465c2d
Improve: Hoist the SplitMix64 avalanche into the shared types
ashvardanian Jul 15, 2026
49290e7
Break: Measure the memory fabric in-process, replacing HMAT and kerne…
ashvardanian Jul 15, 2026
35e439b
Add: Bind the measured fabric to Rust and Zig
ashvardanian Jul 15, 2026
a6e9843
Docs: Rewrite the topology guide around the declared-versus-observed …
ashvardanian Jul 15, 2026
6fc9312
Make: Version the duplicated macros in types.hpp
ashvardanian Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add: Bind the measured fabric to Rust and Zig
Both bindings gain a `Fabric` type in their scheduling modules - the
mirrors of the C++ scheduling layer where `measured_fabric` lives, so
no new files appear. Rust exposes `Fabric::new` and
`try_harvest(&mut self, &Topology, &mut ThreadPool)`, keeping the
topology shareable during measurement; Zig exposes `Fabric.init`,
`deinit`, and `tryHarvest(topo, pool)`. Each carries the five queries
- memoryLatency, memoryBandwidth, memoryDistance, memoryLevelIn, and
the levels count - and each module's test harvests through a live pool
and asserts the local edge, the distance anchor, and the pre-harvest
zeros. The Topology-side metric getters and the old
measure-memory-distances entry points are gone from both languages,
matching the C header, and the extern lists are regrouped flat in the
header's declaration order.
  • Loading branch information
ashvardanian committed Jul 15, 2026
commit 35e439b5ee04a6a0ed63d3286dc8d162f5631357
7 changes: 4 additions & 3 deletions rust/forkunion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
//! principles while leveraging NUMA-aware optimizations and CPU-specific busy-waiting instructions.
//!
//! The wrapper mirrors the C++ core's header layout: [`topology`] for the hardware view, [`types`] for
//! portable building blocks, [`allocators`] for NUMA-aware allocation, [`scheduling`] for the thread pool
//! and its dispatch primitives, and [`parallel`] for the Rayon-style parallel iterators. Every public
//! symbol is re-exported here, so `use forkunion::*` resolves exactly as it did before the split.
//! portable building blocks, [`allocators`] for NUMA-aware allocation, [`scheduling`] for the thread pool,
//! its dispatch primitives, and the measured memory fabric, and [`parallel`] for the Rayon-style parallel
//! iterators. Every public symbol is re-exported here, so `use forkunion::*` resolves exactly as it did
//! before the split.

#![no_std]

Expand Down
159 changes: 152 additions & 7 deletions rust/forkunion/scheduling.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
//! The thread pool and its dispatch primitives - spawn, scoped joins, and parallel loops.
//! The thread pool and its dispatch primitives - spawn, scoped joins, parallel loops - and the
//! measured memory fabric those pools can harvest.
//!
//! Owns the `fu_pool_*` FFI; mirrors the C++ `flat`/scheduling layer.
//! Owns the `fu_pool_*` and `fu_fabric_*` FFI; mirrors the C++ `flat`/`distributed` scheduling layer.

use crate::parallel::{ParallelIterator, ParallelSchedule};
use crate::topology::{CallerExclusivity, Capabilities, Error, Topology};
use crate::topology::{
CallerExclusivity, Capabilities, ComputeDomain, Error, MemoryDomain, Topology,
};
use crate::types::{IndexedSplit, Prong, SafePtr, SyncMutPtr};
use core::ffi::{c_char, c_int, c_void};

// C FFI declarations
extern "C" {
// Pool lifecycle & introspection
fn fu_pool_new(name: *const c_char, allowed: u32) -> *mut c_void;
fn fu_pool_delete(pool: *mut c_void);
fn fu_pool_spawn(
Expand Down Expand Up @@ -37,7 +38,6 @@ extern "C" {
fn fu_pool_sleep(pool: *mut c_void, micros: usize);
fn fu_pool_terminate(pool: *mut c_void);

// Parallel dispatch
#[allow(dead_code)]
fn fu_pool_for_threads(
pool: *mut c_void,
Expand All @@ -63,7 +63,6 @@ extern "C" {
context: *mut c_void,
);

// Generation tokens
fn fu_pool_unsafe_for_threads(
pool: *mut c_void,
callback: extern "C" fn(*mut c_void, usize, usize),
Expand All @@ -72,6 +71,27 @@ extern "C" {
fn fu_pool_is_complete(pool: *mut c_void, generation: usize) -> c_int;
fn fu_pool_unsafe_join(pool: *mut c_void, generation: usize);
fn fu_pool_capabilities(pool: *mut c_void) -> u32;

fn fu_fabric_new() -> *mut c_void;
fn fu_fabric_delete(fabric: *mut c_void);
fn fu_fabric_harvest(topology: *mut c_void, pool: *mut c_void, fabric: *mut c_void) -> c_int;
fn fu_fabric_memory_latency(
fabric: *mut c_void,
compute_domain_index: usize,
memory_domain_index: usize,
) -> usize;
fn fu_fabric_memory_bandwidth(
fabric: *mut c_void,
compute_domain_index: usize,
memory_domain_index: usize,
) -> usize;
fn fu_fabric_memory_distance(
fabric: *mut c_void,
compute_domain_index: usize,
memory_domain_index: usize,
) -> usize;
fn fu_fabric_memory_level_in(fabric: *mut c_void, memory_domain_index: usize) -> usize;
fn fu_fabric_memory_levels_count(fabric: *mut c_void) -> usize;
}

/// Minimalistic, fixed-size thread-pool for blocking scoped parallelism.
Expand Down Expand Up @@ -746,6 +766,108 @@ impl Drop for ThreadPool {
}
}

/// The measured memory fabric - what this process observed, as opposed to the structure a
/// [`Topology`] declares. 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 `try_harvest` pipeline: a [`Topology`] is harvested first and stays immutable
/// (and shareable), a [`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`](Self::memory_levels_count) answers 1.
///
/// # Examples
///
/// ```rust,no_run
/// use forkunion::*;
/// let topology = Topology::new().unwrap();
/// let mut pool = spawn(&topology, 4);
/// let mut fabric = Fabric::new().unwrap();
/// if fabric.try_harvest(&topology, &mut pool) {
/// let local = topology.local_memory_of(ComputeDomain(0));
/// assert!(fabric.memory_latency(ComputeDomain(0), local) > 0);
/// }
/// ```
pub struct Fabric {
inner: *mut c_void,
}

unsafe impl Send for Fabric {}
unsafe impl Sync for Fabric {}

impl Fabric {
/// Creates an empty, unharvested fabric.
pub fn new() -> Result<Fabric, Error> {
let inner = unsafe { fu_fabric_new() };
if inner.is_null() {
return Err(Error::CreationFailed);
}
Ok(Fabric { inner })
}

/// Measures the memory fabric through the pool's pinned workers, replacing any previous
/// harvest; the `topology` is only read.
///
/// Returns `false` on allocation failure, or for a pool whose workers are not pinned per
/// domain - one restricted below [`Capabilities::PLACE_MEMORY_ON_DOMAIN`] or pinned via
/// [`ThreadPool::try_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.
pub fn try_harvest(&mut self, topology: &Topology, pool: &mut ThreadPool) -> bool {
unsafe { fu_fabric_harvest(topology.raw(), pool.inner, self.inner) != 0 }
}

/// Returns the measured dependent-load latency (nanoseconds) on an edge - the best recording;
/// 0 before a harvest, for an edge no worker could reach, or an out-of-range index.
pub fn memory_latency(
&self,
compute_domain: ComputeDomain,
memory_domain: MemoryDomain,
) -> usize {
unsafe { fu_fabric_memory_latency(self.inner, compute_domain.get(), memory_domain.get()) }
}

/// 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.
pub fn memory_bandwidth(
&self,
compute_domain: ComputeDomain,
memory_domain: MemoryDomain,
) -> usize {
unsafe { fu_fabric_memory_bandwidth(self.inner, compute_domain.get(), memory_domain.get()) }
}

/// 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.
pub fn memory_distance(
&self,
compute_domain: ComputeDomain,
memory_domain: MemoryDomain,
) -> usize {
unsafe { fu_fabric_memory_distance(self.inner, compute_domain.get(), memory_domain.get()) }
}

/// 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.
pub fn memory_level_in(&self, memory_domain: MemoryDomain) -> usize {
unsafe { fu_fabric_memory_level_in(self.inner, memory_domain.get()) }
}

/// Returns the number of distinct derived memory tiers, the memory-axis twin of
/// [`Topology::compute_levels_count`]; 1 on single-tier systems and before a harvest.
pub fn memory_levels_count(&self) -> usize {
unsafe { fu_fabric_memory_levels_count(self.inner) }
}
}

impl Drop for Fabric {
fn drop(&mut self) {
unsafe { fu_fabric_delete(self.inner) };
}
}

/// A synchronization guard that waits for all threads to finish the broadcasted closure.
///
/// The lifecycle is keyed on the pool's exclusivity:
Expand Down Expand Up @@ -1422,6 +1544,29 @@ mod tests {
}
}

#[cfg_attr(miri, ignore)]
#[test]
fn fabric_harvest_fills_edges() {
let topology = Topology::new().unwrap();
let mut pool = spawn(&topology, hw_threads());
let mut fabric = Fabric::new().unwrap();

// An unharvested fabric answers zeros and a single tier.
assert_eq!(fabric.memory_latency(ComputeDomain(0), MemoryDomain(0)), 0);
assert_eq!(fabric.memory_levels_count(), 1);

if !fabric.try_harvest(&topology, &mut pool) {
return; // ? A flat pool without domain placement has no fabric to walk
}
// Every reachable edge must carry sane observations; emulated-NUMA guests may
// measure equal local and remote costs, so nothing stronger is asserted.
let local = topology.local_memory_of(ComputeDomain(0));
assert!(fabric.memory_latency(ComputeDomain(0), local) > 0);
assert!(fabric.memory_bandwidth(ComputeDomain(0), local) > 0);
assert_eq!(fabric.memory_distance(ComputeDomain(0), local), 10);
assert!(fabric.memory_levels_count() >= 1);
}

#[cfg_attr(miri, ignore)]
#[test]
fn generation_raw_unsafe_api() {
Expand Down
75 changes: 3 additions & 72 deletions rust/forkunion/topology.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,17 @@

use core::ffi::{c_char, c_int, c_void};

// C FFI declarations
extern "C" {
// Library metadata
fn fu_version_major() -> c_int;
fn fu_version_minor() -> c_int;
fn fu_version_patch() -> c_int;
fn fu_comptime_capabilities() -> u32;
fn fu_runtime_capabilities() -> u32;
fn fu_name_capabilities(caps: u32, buf: *mut c_char, len: usize) -> usize;

// Topology handle lifecycle
fn fu_topology_new() -> *mut c_void;
fn fu_topology_delete(topology: *mut c_void);

// Compute topology
fn fu_logical_cores_count_in(topology: *mut c_void, compute_domain_index: usize) -> usize;
fn fu_logical_cores_count(topology: *mut c_void) -> usize;
fn fu_compute_domains_count(topology: *mut c_void) -> usize;
Expand All @@ -27,37 +23,14 @@ extern "C" {
fn fu_compute_capacity_in(topology: *mut c_void, compute_domain_index: usize) -> usize;
fn fu_compute_cache_bytes_in(topology: *mut c_void, compute_domain_index: usize) -> usize;

// Memory topology
fn fu_memory_domains_count(topology: *mut c_void) -> usize;
fn fu_memory_level_in(topology: *mut c_void, memory_domain_index: usize) -> usize;
fn fu_memory_levels_count(topology: *mut c_void) -> usize;

// Affinity
fn fu_local_memory_of(topology: *mut c_void, compute_domain_index: usize) -> usize;
fn fu_memory_distance(
topology: *mut c_void,
compute_domain_index: usize,
memory_domain_index: usize,
) -> usize;
fn fu_memory_bandwidth(
topology: *mut c_void,
compute_domain_index: usize,
memory_domain_index: usize,
) -> usize;
fn fu_memory_latency(
topology: *mut c_void,
compute_domain_index: usize,
memory_domain_index: usize,
) -> usize;

fn fu_volume_ram_in(topology: *mut c_void, memory_domain_index: usize) -> usize;
fn fu_volume_ram(topology: *mut c_void) -> usize;
fn fu_volume_huge_pages_in(topology: *mut c_void, memory_domain_index: usize) -> usize;
fn fu_volume_huge_pages(topology: *mut c_void) -> usize;
fn fu_huge_pages_count_in(topology: *mut c_void, memory_domain_index: usize) -> usize;
fn fu_huge_pages_count(topology: *mut c_void) -> usize;

// Allocation
fn fu_memory_domain_id_at_index(topology: *mut c_void, memory_domain_index: usize) -> i32;
}

Expand Down Expand Up @@ -396,51 +369,14 @@ impl Topology {
MemoryDomainId(unsafe { fu_memory_domain_id_at_index(self.inner, memory_domain.get()) })
}

/// Returns the performance level of a memory domain (lower = faster: HBM < DDR < CXL).
pub fn memory_level_in(&self, memory_domain: MemoryDomain) -> usize {
unsafe { fu_memory_level_in(self.inner, memory_domain.get()) }
}

/// Returns the number of distinct memory tiers, the memory-axis twin of
/// [`compute_levels_count`](Self::compute_levels_count).
///
/// Reports 1 on single-tier systems, and 2+ where HBM, DDR, and CXL are mixed.
pub fn memory_levels_count(&self) -> usize {
unsafe { fu_memory_levels_count(self.inner) }
}

/// Returns the memory domain nearest a given compute domain (its local allocation target).
///
/// Performance - tiers, latencies, bandwidths, distances - is not the topology's to declare:
/// harvest a [`Fabric`](crate::Fabric) to measure it in-process.
pub fn local_memory_of(&self, compute_domain: ComputeDomain) -> MemoryDomain {
MemoryDomain(unsafe { fu_local_memory_of(self.inner, compute_domain.get()) })
}

/// Returns the relative access distance from a compute domain to a memory domain (10 = local).
pub fn memory_distance(
&self,
compute_domain: ComputeDomain,
memory_domain: MemoryDomain,
) -> usize {
unsafe { fu_memory_distance(self.inner, compute_domain.get(), memory_domain.get()) }
}

/// Returns the HMAT read bandwidth (MB/s) from a compute domain to a memory domain, or 0 if unknown.
pub fn memory_bandwidth(
&self,
compute_domain: ComputeDomain,
memory_domain: MemoryDomain,
) -> usize {
unsafe { fu_memory_bandwidth(self.inner, compute_domain.get(), memory_domain.get()) }
}

/// Returns the HMAT read latency (nanoseconds) from a compute domain to a memory domain, or 0 if unknown.
pub fn memory_latency(
&self,
compute_domain: ComputeDomain,
memory_domain: MemoryDomain,
) -> usize {
unsafe { fu_memory_latency(self.inner, compute_domain.get(), memory_domain.get()) }
}

/// Returns the RAM volume (bytes) held by a given memory domain (0 if out of range).
pub fn volume_ram_in(&self, memory_domain: MemoryDomain) -> usize {
unsafe { fu_volume_ram_in(self.inner, memory_domain.get()) }
Expand Down Expand Up @@ -559,12 +495,10 @@ pub(crate) mod tests {
let compute_domains = topology.compute_domains_count();
let compute_levels = topology.compute_levels_count();
let memory_domains = topology.memory_domains_count();
let memory_levels = topology.memory_levels_count();
assert!(compute_domains > 0 && memory_domains > 0);

// Levels are dense ranks over domains, so they can never outnumber them.
assert!(compute_levels <= compute_domains);
assert!(memory_levels <= memory_domains);

for domain in (0..compute_domains).map(ComputeDomain) {
assert!(topology.compute_level_in(domain) < compute_levels.max(1));
Expand All @@ -573,9 +507,6 @@ pub(crate) mod tests {
let _capacity = topology.compute_capacity_in(domain);
let _cache_bytes = topology.compute_cache_bytes_in(domain);
}
for domain in (0..memory_domains).map(MemoryDomain) {
assert!(topology.memory_level_in(domain) < memory_levels.max(1));
}

// Out-of-range indices must saturate to 0 rather than trap or read past the topology.
assert_eq!(
Expand Down
1 change: 1 addition & 0 deletions zig/forkunion.zig
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ pub const ShardedArray = allocators.ShardedArray;

// scheduling
pub const Pool = scheduling.Pool;
pub const Fabric = scheduling.Fabric;

test { // pull each module's tests into `zig build test`
_ = @import("forkunion/topology.zig");
Expand Down
Loading