# Quickstart

> ForkUnion for C++

__`ForkUnion`__ is dead-simple to use!
There is no nested parallelism, exception handling, or "future promises"; they are banned.
A pool runs one dispatch at a time and refuses any other with the `contended` status, including one from inside its own callbacks.
The thread pool itself has a few core operations:

- `spawn` to initialize worker threads, and
- `for_threads` to launch a blocking callback on all threads.

Higher-level APIs for index-addressable tasks are also available:

- `for_n` - for individual evenly-sized tasks,
- `for_n_dynamic` - for individual unevenly-sized tasks,
- `for_slices` - for contiguous runs of evenly-sized tasks, one per worker.

For additional flow control and tuning, following helpers are available:

- `nap` - to park workers on the CPU's address monitor, waking with no syscall,
- `sleep(microseconds)` - to block workers in the kernel for long idle periods, with 0 waiting for the next dispatch,
- `terminate` - to kill the threads before the destructor is called,
- `unsafe_for_threads` - to broadcast a callback without blocking, returning a generation token,
- `unsafe_join` - to block until the completion of a broadcasted generation,
- `is_complete` - to poll a generation token for completion without blocking.

Every dispatch is identified by a `generation` token.
On `caller_exclusive_k` pools you can dispatch, overlap your own work, poll `is_complete`, and join - the classic poll-then-join pattern.
On `caller_inclusive_k` pools the calling thread owes one slice of the work, which only runs inside `unsafe_join`, so completion can't be reached by polling alone.
The same rule shapes the RAII guard returned by `for_threads` and, in C++, the `for_n` family: the workers start at the guard's construction on every pool, and on inclusive pools the caller's own slice runs at `join` or destruction.

On `caller_exclusive_k` pools thread 0 may also fork from inside its own slice, at any depth: such a fork waits for every other slice of the one enclosing it, runs on all threads, and returns complete.
So `unsafe_for_threads` can hand a whole multi-step call to thread 0, which forks the rest of the pool as it goes, while the caller only polls a status word the call stores at its end.
That is how one manager thread keeps several exclusive pools busy with no queue and no extra thread.
A join on such a pool waits for whatever dispatch runs, since the call may end before its submitter stores the token.

In C++ it provides [NUMA extensions](https://www.unum.cloud/docs/forkunion/cpp/numa.md).
That includes the `colocated_pool` analog of the `flat_pool` and the `domain_allocator_t` for allocating memory in a specific memory domain.
Those are out-of-the-box compatible with the higher-level APIs.
Most interestingly, for Big Data applications, a higher-level `distributed_pool` class addresses and balances the work across all compute domains.

> `domain_allocator_t` is backed by `linux_numa_allocator_t`, `freebsd_numa_allocator_t`, or `windows_numa_allocator_t`.

To integrate into your C++ project, either copy the `c/` and `include/` directories into your project and add both to the include path, add a Git submodule, or CMake.
The `forkunion.hpp` umbrella is a list of `#include`s; the implementation lives beside it in `include/forkunion/`, one header per concern, so a platform backend can be read or replaced on its own.
For a Git submodule, run:

```bash
git submodule add https://github.com/ashvardanian/ForkUnion.git extern/forkunion
```

Alternatively, using CMake:

```cmake
FetchContent_Declare(
    forkunion
    GIT_REPOSITORY https://github.com/ashvardanian/ForkUnion
    GIT_TAG v4.0.0-rc.1
)
FetchContent_MakeAvailable(forkunion)
target_link_libraries(your_target PRIVATE forkunion::header)
```

Then, include the header in your C++ code:

```cpp
#include <forkunion.hpp>    // `flat_pool_t`
#include <cstdio>           // `stderr`
#include <cstdlib>          // `EXIT_SUCCESS`

namespace fu = ashvardanian::forkunion;

int main() {
    alignas(fu::default_alignment_k) fu::flat_pool_t pool;
    if (fu::failed(pool.spawn(fu::allowed_cores_count()))) {
        std::fprintf(stderr, "Failed to fork the threads\n");
        return EXIT_FAILURE;
    }

    // Dispatch a callback to each thread in the pool
    pool.for_threads([&](std::size_t thread_index) noexcept {
        std::printf("Hello from thread # %zu (of %zu)\n", thread_index + 1, pool.threads_count());
    });

    // Execute 1000 tasks in parallel, expecting them to have comparable runtimes
    // and mostly co-locating subsequent tasks on the same thread. Analogous to:
    //
    //      #pragma omp parallel for schedule(static)
    //      for (int i = 0; i < 1000; ++i) { ... }
    //
    // You can also think about it as a shortcut for the `for_slices` + `for`.
    pool.for_n(1000, [](std::size_t task, fu::thread_in_domain_t) noexcept {
        std::printf("Running task %zu of 1000\n", task + 1);
    });
    pool.for_slices(1000, [](fu::tasks_range_t range, fu::thread_in_domain_t) noexcept {
        std::printf("Running run [%zu, %zu)\n", range.first, range.first + range.count);
    });

    // Like `for_n`, but each thread greedily steals tasks, without waiting for  
    // the others or expecting individual tasks to have same runtimes. Analogous to:
    //
    //      #pragma omp parallel for schedule(dynamic, 1)
    //      for (int i = 0; i < 3; ++i) { ... }
    pool.for_n_dynamic(3, [](std::size_t task, fu::thread_in_domain_t) noexcept {
        std::printf("Running dynamic task %zu of 3\n", task + 1);
    });
    return EXIT_SUCCESS;
}
```

For advanced usage, refer to the [NUMA section below](https://www.unum.cloud/docs/forkunion/cpp/numa.md).
Every kernel and ISA facility the library uses is detected by default.
CMake pins the four kernel facilities with an `AUTO`/`ON`/`OFF` tri-state like `-D FORKUNION_WITH_PLACE_MEMORY_ON_DOMAIN=ON` or `-D FORKUNION_WITH_PLACE_THREADS_BY_AFFINITY=OFF`.
Finer preprocessor gates - like the cache-line hints `FORKUNION_WITH_DEMOTE_CACHE_LINES`, which pushes a just-written line away from the core via x86 `CLDEMOTE` or Arm `DC CVAC`, and `FORKUNION_WITH_PROMOTE_CACHE_LINES`, which pulls a line in with write intent via x86 `PREFETCHW`, Arm `PRFM PSTL1KEEP`, or RISC-V `prefetch.w` - accept the same overrides as compile definitions.
Call `fu_comptime_capabilities()` to see what survived the build, `fu_runtime_capabilities()` to see what the machine underneath actually offers, and `fu_name_capabilities()` to render either mask as text - every binding exposes the same trio, Rust and Mojo spelling it `runtime_capabilities()` and Zig `runtimeCapabilities` with `runtimeCapabilitiesString` to render, alongside a version accessor: `fu_version_major`/`_minor`/`_patch` in C, `version()` in Rust, Zig, and Mojo, and the `FORKUNION_VERSION_*` macros in C++.

Every all-caps name starts with the full project name, `FORKUNION_`.
A trailing `_` marks a name as internal: it may change in any release, and nothing outside this repository may define or test it.
A name without it is a public contract, either a switch you may set or a value you may read.

| Family                   | Form                            | Example                      |
| :----------------------- | :------------------------------ | :--------------------------- |
| ISA capability bit       | `FORKUNION_TARGET_<CAPABILITY>` | `FORKUNION_TARGET_X86_PAUSE` |
| Dispatch mode            | `FORKUNION_RUNTIME_DISPATCH`    |                              |
| Optional feature         | `FORKUNION_WITH_<FEATURE>`      | `FORKUNION_WITH_TOPOLOGY`    |
| Permission for a liberty | `FORKUNION_ALLOW_<LIBERTY>`     | `FORKUNION_ALLOW_UNSAFE`     |
| Architecture fact        | `FORKUNION_ARCH_<ARCH>_`        | `FORKUNION_ARCH_X8664_`      |
| Operating-system fact    | `FORKUNION_OS_<OS>_`            | `FORKUNION_OS_LINUX_`        |
| Toolchain fact           | `FORKUNION_HAS_<FEATURE>_`      | `FORKUNION_HAS_INLINE_ASM_`  |

Architectures are spelled `X8664`, `ARM64`, `RISCV64` and `WASM`.
Every name in these families is always defined, as 0 or 1, and tested with `#if`, never with `defined(...)`.

`#include <forkunion/types.hpp>`

`#include <forkunion/flat.hpp>`

`#include <forkunion/capabilities.hpp>`

`#include <forkunion/logging.hpp>`

## status_t

Why a call failed.

```cpp
enum status_t
```

Success is `0`; every failure is negative.

Every enumerator takes its value from `[fu_status_t](https://www.unum.cloud/docs/forkunion/c/quickstart.md#fu_status_t)` in `forkunion/forkunion.h`, so the C++ mirror and the ABI cannot drift - there is nothing to keep in step and nothing to assert.

Causes are collapsed on purpose where the remedy is the same, and split where it differs.

Statuses fall into four categories, with no interruptions to report. Resources: [`bad_alloc_k`](#bad_alloc_k), [`thread_refused_k`](#thread_refused_k), [`topology_unavailable_k`](#topology_unavailable_k), [`permission_denied_k`](#permission_denied_k), [`library_missing_k`](#library_missing_k) and [`symbol_missing_k`](#symbol_missing_k). Input: [`invalid_argument_k`](#invalid_argument_k). State: [`capacity_exhausted_k`](#capacity_exhausted_k), [`config_mismatch_k`](#config_mismatch_k), [`already_spawned_k`](#already_spawned_k), [`not_spawned_k`](#not_spawned_k), [`unsupported_k`](#unsupported_k) and [`contended_k`](#contended_k). Defects: [`unknown_k`](#unknown_k). Any verb that allocates, binds a device or touches a file may also return a Resources status. A verb's own block lists only what its contract adds.

- `success_k` = `fu_success_k`: The call completed and any output holds a meaningful value.
- `unknown_k` = `fu_unknown_k`: No reason was reported, or one this build does not name.
- `bad_alloc_k` = `fu_bad_alloc_k`: An allocation or mapping failed; a smaller request may succeed.
- `capacity_exhausted_k` = `fu_capacity_exhausted_k`: A fixed ceiling was reached, so unlike [`bad_alloc_k`](#bad_alloc_k) a smaller request will not help.
- `invalid_argument_k` = `fu_invalid_argument_k`: Any argument fault - a zero count, an out-of-range index, a byte count that would wrap - as one code, because the remedy is the same.
- `config_mismatch_k` = `fu_config_mismatch_k`: The handles or the pool kind cannot serve this call together.
- `already_spawned_k` = `fu_already_spawned_k`: The pool is already spawned; terminate it first.
- `not_spawned_k` = `fu_not_spawned_k`: The pool was never spawned; spawn it first.
- `thread_refused_k` = `fu_thread_refused_k`: The OS declined to create a thread - a resource limit, or permissions.
- `topology_unavailable_k` = `fu_topology_unavailable_k`: The machine could not be described; transient if its CPU set changed mid-probe.
- `permission_denied_k` = `fu_permission_denied_k`: A privileged operation was declined.
- `unsupported_k` = `fu_unsupported_k`: This build or this machine has no such facility.
- `library_missing_k` = `fu_library_missing_k`: Binding-only: nothing to load, or a different major version.
- `symbol_missing_k` = `fu_symbol_missing_k`: Binding-only: the loaded core is missing a symbol.
- `contended_k` = `fu_contended_k`: The pool is running another dispatch: another caller's, or the one this call runs inside.

Notes: A refused affinity is not here. The pool still partitions work by domain when the kernel declines to pin; `all_threads_pinned()` is where that is reported.

## caller_exclusivity_t

Defines the in- and exclusivity of the calling thread in for the executing task.

```cpp
enum caller_exclusivity_t
```

This enum affects how the join is performed. If the caller is inclusive, 1/Nth of the call will be executed by the calling thread, as opposed to workers, and the join will happen inside of the calling scope.

- `caller_inclusive_k` = `0`
- `caller_exclusive_k` = `1`

See also: caller_inclusive_k and [`caller_exclusive_k`](#caller_exclusive_k)

## capabilities_t

Describes all the special library features, both those compiled in and those found here.

```cpp
enum capabilities_t
```

One bit per facility, and two accessors ask two questions of the same bit. `comptime_capabilities()` reports whether the code for a facility was __built:__ [`capability_place_huge_pages_on_domain_k`](#capability_place_huge_pages_on_domain_k) there means we compiled the path that asks the kernel for them. `runtime_capabilities()` reports whether the facility is __present__ on this machine: the same bit means the kernel is offering them now.

Neither implies the other. A binary that built [`capability_place_memory_on_domain_k`](#capability_place_memory_on_domain_k) runs well on a single-node box, where the runtime accessor never sets that bit; and a machine with four NUMA nodes reports none of them to a build that left the topology out. A facility is usable here only when both accessors agree: the honest set is `comptime_capabilities() & runtime_capabilities()`.

- `capabilities_unknown_k` = `0`: No facility at all.
- `capability_x86_pause_k` = `1 << 0`: The `PAUSE` spin hint, on every x86 since the Pentium 4: a short pipeline stall that keeps a busy-wait from flooding the load ports and eases the sibling hardware thread.
- `capability_x86_tpause_k` = `1 << 1`: `TPAUSE` sleeps the core until a deadline rather than spinning - the `WAITPKG` feature, the CPUID leaf 7, subleaf 0, ECX bit 5, on Sapphire Rapids, Alder Lake and their successors.
- `capability_arm64_yield_k` = `1 << 2`: The `YIELD` hint, on every AArch64: releases the pipeline to a sibling hardware thread and costs nothing where there is none.
- `capability_arm64_wfet_k` = `1 << 3`: `WFET` sleeps the core until a deadline or an event on the monitored line - `FEAT_WFxT`, Armv8.7, read from `ID_AA64ISAR2_EL1` on Linux and `hw.optional.arm.FEAT_WFxT` on Apple.
- `capability_riscv64_pause_k` = `1 << 4`: The `PAUSE` spin hint of the `Zihintpause` extension - encoded as a `FENCE` every hart accepts, so it is reported on every RISC-V.
- `capability_riscv64_wrs_k` = `1 << 5`: `WRS.STO` sleeps the hart until a reservation breaks or a short timeout - the `Zawrs` extension, attested by the kernel's `hwprobe`.
- `capability_os_threads_k` = `1 << 6`: Own the raw OS thread handle instead of a `std::thread` - the substrate every thread lever below stands on.
- `capability_topology_k` = `1 << 7`: Enumerate this machine's cores, compute domains, and memory domains - the root every placement needs.
- `capability_place_threads_by_affinity_k` = `1 << 8`: Bind a thread to a set of cores, choosing where it runs.
- `capability_place_threads_by_core_class_k` = `1 << 9`: Steer a thread onto a class of core at creation, choosing where it runs.
- `capability_reschedule_threads_by_class_k` = `1 << 10`: Reclass a thread's scheduler to sleep or wake it, choosing when it runs.
- `capability_place_memory_on_domain_k` = `1 << 11`: Place a buffer's pages on a chosen memory domain.
- `capability_place_huge_pages_on_domain_k` = `1 << 12`: Place larger-than-base pages on a chosen memory domain.
- `capability_huge_transparent_pages_k` = `1 << 13`: The kernel promotes base pages to huge pages on its own.
- `capability_colocate_pools_on_domain_k` = `1 << 14`: Compile the domain-aware `colocated_pool` and `distributed_pool`.
- `capability_x86_cldemote_k` = `1 << 15`: `CLDEMOTE` moves a just-written line from this core's private caches toward the shared LLC and retains it there.
- `capability_arm64_dc_cvac_k` = `1 << 16`: `DC CVAC` cleans a dirty line to the coherency point, AArch64's nearest thing to a demote: the next claimer's snoop finds a clean line, forcing no dirty intervention.
- `capability_riscv64_zicbom_k` = `1 << 17`: The kernel enabled user-mode Zicbom cache-block management - `senvcfg.CBCFE` - attested through `hwprobe`, the only sound signal, since a compile-time `+zicbom` proves nothing about the kernel.
- `capability_x86_cmpccxadd_k` = `1 << 18`: `CMPCCXADD`, the conditional atomic add - CPUID leaf 7, subleaf 1, EAX bit 7, on Sierra Forest, Diamond Rapids and their successors: a bounded claim as one instruction instead of a compare-exchange loop.
- `capability_x86_raoint_k` = `1 << 19`: RAO-INT `aadd`, `aand`, `aor` and `axor` - CPUID leaf 7, subleaf 1, EAX bit 3, on Grand Ridge: no-return atomics executed at the shared cache rather than pulling the line; weakly ordered, so relaxed only.
- `capability_arm64_lse_k` = `1 << 20`: Armv8.1 `FEAT_LSE:` `swp`, `cas`, `ldadd` and the no-return `st*` forms - one instruction per read-modify-write instead of a load-exclusive loop.
- `capability_arm64_rcpc_k` = `1 << 21`: Armv8.3 `FEAT_LRCPC:` `ldapr`, the RCpc acquiring load that needn't wait for the core's earlier release stores the way RCsc `ldar` may.
- `capability_riscv64_zacas_k` = `1 << 22`: `Zacas:` `amocas`, compare-and-swap as one instruction instead of an `lr` and `sc` loop, attested by the kernel's `hwprobe`.
- `capability_riscv64_atomic_k` = `1 << 23`: The A extension: `lr`, `sc` and the `amo*` read-modify-writes, the RISC-V baseline every Linux ABI guarantees, so the detector sets it unconditionally.
- `capability_arm64_prfm_k` = `1 << 24`: The AArch64 `PRFM` prefetch hints, base ISA that can never fault, so the detector reports them on every AArch64 core.
- `capability_riscv64_zicbop_k` = `1 << 25`: The `Zicbop` prefetches, `prefetch.r` and `prefetch.w`, encoded as `ORI` hints every hart accepts, so the detector reports them on every RISC-V core.
- `capability_riscv64_zabha_k` = `1 << 26`: `Zabha:` the byte and halfword `amo` forms, and with `Zacas` the byte and halfword `amocas`, attested by the kernel's `hwprobe`.
- `capability_any_yield_k` = `capability_x86_pause_k | capability_x86_tpause_k | capability_arm64_yield_k |
                             capability_arm64_wfet_k | capability_riscv64_pause_k | capability_riscv64_wrs_k`: Composite mask of every busy-wait waiter bit above, to enumerate the ones a machine offers in one intersection with `runtime_capabilities()`.

See also: [comptime_capabilities](#comptime_capabilities) and `[runtime_capabilities](#runtime_capabilities)`

## pool_kind_t

Which of the three pool shapes an instance is, independent of the waiter it uses.

```cpp
enum pool_kind_t
```

This is the shape discriminator, not a capability: [`capability_colocate_pools_on_domain_k`](#capability_colocate_pools_on_domain_k) says the colocated and distributed shapes were __compiled__, while this says which one a pool __is__.

- `unknown_k` = `0`: No pool constructed - the shape is undecided until a spawn builds one.
- `flat_k`: No domain awareness - one worker set over all cores, as if the machine were uniform.
- `colocated_k`: Pinned to a single compute domain, so its workers share a cache and a memory domain.
- `distributed_k`: Spans every compute domain, replicating hot state per memory domain.

## limited_array

A fixed-capacity array with inline storage, so it never allocates.

```cpp
template <typename value_type_,
          std::size_t capacity_>
class limited_array
```

Sized for the small, bounded lists a machine hands us - the huge page sizes of a NUMA node, the processor groups of a Windows box. Refuses to grow past `capacity_k` rather than truncating in silence, because a list quietly cut short is a topology quietly misreported.

- `capacity_k` `std::size_t`

See also: [dynamic_array](#dynamic_array) when the count is only known at runtime.

## dynamic_padded_array

A `[dynamic_array](#dynamic_array)` with elements at a caller-chosen stride, each on its own cache line.

```cpp
template <typename object_type_,
          typename allocator_type_>
class dynamic_padded_array
```

Move-only and owning like `std::unique_ptr<T[]>`, but it spaces objects by `stride()` bytes rather than `sizeof(T)` and honours over-alignment, so an `alignas(128)` pool cell never false-shares and never lands in under-aligned storage. It also accepts an [`allocate_at_least`](https://www.unum.cloud/docs/forkunion/cpp/numa.md#allocate_at_least) allocator, the way the NUMA backends hand back more bytes than asked.

See also: [dynamic_array](#dynamic_array) for the packed counterpart at natural alignment.

## tasks_range

A half-open `[first, first + count)` run of task indices, the "what work" of a dispatch.

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

Iterable, so a callback reads `for (auto task : range)` rather than rebuilding the bounds. The exclusive end is `first + count`; an idle thread receives a range with `count == 0`.

- `first` `index_t`: The first task index in the run.
- `count` `index_t`: How many tasks the run covers; zero means an idle thread.

## indexed_split

Splits a range of tasks into fair-sized runs for each thread.

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

The first `(tasks % threads)` runs have size `ceil(tasks / threads)`. The remaining `tasks - (tasks % threads)` runs have size `floor(tasks / threads)`. Has the convenient added property that the difference between the largest and smallest run size is at most 1, which can be used in some ordering algorithms.

See also: [https://lemire.me/blog/2025/05/22/dividing-an-array-into-fair-sized-chunks/](https://lemire.me/blog/2025/05/22/dividing-an-array-into-fair-sized-chunks/)

## coprime_permutation_range

Iterator range over integers using a stride that is co-prime with length.

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

- Constant-time dereference: two integer ops and a branchless wrap-around.
- Every value appears exactly once before `end()`.

```
coprime_permutation_range<> perm(start, length, seed);
for (auto v : perm) steal_from(v);
```

## invoke_for_slices

Wraps the metadata needed for [`for_slices`](#for_slices) APIs for `[broadcast_join](#broadcast_join)` compatibility.

```cpp
template <typename fork_type_,
          typename index_type_>
class invoke_for_slices
```

## invoke_for_n

Wraps the metadata needed for [`for_n`](#for_n) APIs for `[broadcast_join](#broadcast_join)` compatibility.

```cpp
template <typename fork_type_,
          typename index_type_>
class invoke_for_n
```

## dynamic_claim

One thread's private cursor into its own slice of a [`for_n_dynamic`](#for_n_dynamic) dispatch.

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

A single shared counter serializes an entire dispatch: only one core may own its cache line at a time, so no dispatch retires tasks faster than that line circulates. Handing every thread its own cursor turns the common claim into an __uncontended__ read-modify-write on a line nobody else touches, which is roughly fifty times cheaper. The line is only shared once a thread runs dry and starts helping a neighbour, which is exactly when the extra cost is worth paying.

Pad these to a full cache line - two cursors sharing a line would reintroduce the very traffic the split exists to avoid.

- `next` `std::atomic<index_type_>`: Next task in this slice; only ever grows, and may overshoot `end` by `threads`.
- `end` `index_type_`: One past this slice's last task; written once before the dispatch, then read-only.

See also: [invoke_for_n_dynamic](#invoke_for_n_dynamic) hands each thread a slice; idle threads drain their neighbours'.

See also: [dynamic_padded_array](#dynamic_padded_array), which spaces them by the pool's alignment.

## invoke_for_n_dynamic

Wraps the metadata needed for [`for_n_dynamic`](#for_n_dynamic) APIs for `[broadcast_join](#broadcast_join)` compatibility.

```cpp
template <typename pool_type_,
          typename fork_type_,
          typename index_type_>
class invoke_for_n_dynamic
```

#### Scheduling Logic

Tasks are split into one contiguous slice per thread. A thread first drains its own slice, then walks the others in a `[coprime_permutation_range](#coprime_permutation_range)` order and drains theirs, one task per claim. Claiming one task at a time is what preserves the makespan guarantee of greedy list scheduling: a thread can never be handed a batch of tasks that turn out to be expensive, because it is never handed a batch. Claiming from a __private__ cursor is what makes that guarantee affordable.

Probing the neighbours in a coprime order rather than linearly keeps two drained threads from descending on the same victim, which would serialize them on one line for no reason.

#### Overflow Considerations

If we run a default for-loop at 1 Billion times per second on a 64-bit machine, then every 585 years of computational time we will wrap around the `std::size_t` capacity for the task index. In case we `n + thread >= std::size_t(-1)`, a simple condition won't be enough. Alternatively, we can make sure, that each thread can do at least one increment of a cursor without worrying about the overflow. The way to achieve that is to preprocess the trailing `threads` of elements externally, before entering this loop!

That trailing reservation also bounds the cursors. Every thread visits a given slice exactly once - each thread's coprime walk is a permutation of all the slices, beginning with its own - and a visit overshoots by at most one increment, since [`drain_claim`](#drain_claim) leaves the moment it reads `>= end` and its read-only probe skips already-drained slices without any increment at all. A cursor therefore never passes `end + threads`.

Two regimes bound that. When `n > threads` the last slice ends at `n - threads`, so no cursor passes `n`. When `n <= threads` every slice is empty and `end == 0`, so no cursor passes `threads`, which may exceed `n` but must be representable anyway for the pool to have spawned. Either way `max(cursor) == max(n, threads)`, and no index type can wrap.

See also: invoke_distributed_for_n_dynamic, which applies the same trick one level up, across compute domains, so a thread exhausts local work before touching a remote node's memory.

## dummy_pool_t

A zero-setup thread-pool that runs every task on the calling thread.

```cpp
struct dummy_pool_t
```

A drop-in __serial__ executor - it satisfies `is_pool` and `is_unsafe_pool` and offers the same scheduling surface as `[flat_pool](#flat_pool)`, namely [`for_threads`](#for_threads), [`for_n`](#for_n), [`for_n_dynamic`](#for_n_dynamic) and [`for_slices`](#for_slices), but with one thread on one compute domain, no [`spawn`](#spawn), and no allocation. [`unsafe_for_threads`](#unsafe_for_threads) runs the fork synchronously as thread 0; everything else composes through `[broadcast_join](#broadcast_join)` exactly as the real pools do. Useful as a serial baseline and as the default executor for the domain-aware containers.

## broadcasted_noop_t

Does nothing on every thread, the default fork for a `[broadcast_join](#broadcast_join)` needing only a join.

```cpp
struct broadcasted_noop_t
```

## log_capabilities_t

Logs a capability mask as a two-row tree, CPU bits then OS facilities, by [`capability_name`](#capability_name).

```cpp
struct log_capabilities_t
```

## dynamic_array

An owning, allocator-aware array whose size is fixed once, at [`resize`](https://www.unum.cloud/docs/forkunion/cpp/topology.md#resize).

```cpp
template <typename value_type_,
          typename allocator_type_ = std::allocator<value_type_>>
class dynamic_array
```

__See also.__

[limited_array](#limited_array) for bounded counts, `[dynamic_padded_array](#dynamic_padded_array)` when elements want their own line.

See also: [limited_array](#limited_array) for bounded counts, `[dynamic_padded_array](#dynamic_padded_array)` when elements want their own line.

## dynamic_array

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

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

```cpp
dynamic_array(dynamic_array && other) noexcept;
```

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

## operator=

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

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

## ~dynamic_array

```cpp
~dynamic_array() noexcept;
```

## resize

Reallocates to `new_size` value-initialized elements, discarding any prior contents.

```cpp
status_t resize(std::size_t const new_size) noexcept;
```

## resize_uninitialized

Like [`resize`](https://www.unum.cloud/docs/forkunion/cpp/topology.md#resize), but skips the zero-fill so the caller controls the first touch.

```cpp
status_t resize_uninitialized(std::size_t const new_size) noexcept;
```

Notes: Trivial value types only - nothing is constructed, so every element must be written before it is read.

See also: [sharded_array::resize_uninitialized](https://www.unum.cloud/docs/forkunion/cpp/numa.md#resize_uninitialized), the same contract.

## reserve

Grows capacity to at least `new_capacity`, preserving the live elements.

```cpp
status_t reserve(std::size_t const new_capacity) noexcept;
```

## push_back

Appends `value`, doubling capacity when full.

```cpp
status_t push_back(value_t const & value) noexcept;
```

## flat_pool

Minimalistic STL-based non-resizable thread-pool for simultaneous blocking tasks.

```cpp
template <typename allocator_type_ = portable_aligned_allocator<std::thread>,
          typename micro_yield_type_ = standard_yield_t,
          template<typename> class atomic_ref_template_ = standard_atomic_ref,
          typename cache_hints_type_ = standard_cache_hints_t,
          typename index_type_ = std::size_t,
          std::size_t alignment_ = default_alignment_k>
class flat_pool
```

This thread-pool __can't:__

- dynamically __resize:__ all threads must be stopped and re-initialized to grow/shrink.
- __re-enter__: a dispatch from inside its own fork, or beside another caller's, is refused.
- __copy/move__: the threads depend on the address of the parent structure.
- handle __exceptions:__ you must `try-catch` them yourself and return `void`.
- __stop__ early: assuming the user can do it better, knowing the task granularity.
- __overflow:__ as all APIs are aggressively tested with smaller index types.This allows this thread-pool to be extremely lightweight and fast, __without__ heap allocations and no expensive abstractions. It only uses `std::thread` and `std::atomic`, but avoids `std::function`, `std::future`, `std::promise`, `std::condition_variable`, that bring unnecessary overhead.

Repeated operations are performed with a __"weak"__ memory model, to leverage in-hardware support for atomic fence-less operations on Arm and IBM Power architectures. Most atomic counters use the "acquire-release" model, and some going further to "relaxed" model.

A minimal example, similar to `#pragma omp parallel` in OpenMP:

```
#include          // `std::printf`
#include         // `EXIT_FAILURE`, `EXIT_SUCCESS`
#include   // `flat_pool_t`

namespace fu = ashvardanian::forkunion;
int main() {
    fu::flat_pool_t pool; // ? Alias to `fu::flat_pool<>` template
    if (fu::failed(pool.spawn(fu::allowed_cores_count()))) return EXIT_FAILURE;
    pool.for_threads([](std::size_t i) noexcept { std::printf("Hi from thread %zu\n", i); });
    return EXIT_SUCCESS;
}
```

Unlike OpenMP, however, separate thread-pools can be created isolating work and resources. This is handy when some logic has to be split between "performance" & "efficiency" cores, between different NUMA nodes, between GUI and background tasks, etc. It may look like this:

```
#include          // `std::printf`
#include         // `EXIT_FAILURE`, `EXIT_SUCCESS`
#include   // `flat_pool_t`

using fu = ashvardanian::forkunion;
int main() {
    fu::flat_pool_t first_pool, second_pool;
    if (!first_pool.spawn(2) || !second_pool.spawn(2, fu::caller_exclusive_k)) return EXIT_FAILURE;
    auto broadcast = second_pool.for_threads([](std::size_t i) noexcept { poll_ssd(i); });
    first_pool.for_threads([](std::size_t i) noexcept { poll_nic(i); });
    broadcast.join(); // ! Wait for the second pool to finish
    return EXIT_SUCCESS;
}
```

#### Concurrency Model

Three roles interact with a pool:

- the __dispatcher__ - the one thread whose dispatch the pool admitted: it dispatches, polls, and joins, while any other dispatch, nested or concurrent, is refused with the zero token; that [`spawn`](#spawn) and [`terminate`](#terminate) overlap no dispatch is contractual and not enforced;
- the __contributors__ - threads executing one slice each per generation: all workers, plus the calling thread itself on [`caller_inclusive_k`](#caller_inclusive_k) pools, whose slice runs inside [`unsafe_join`](#unsafe_join);
- the __pollers__ - any threads calling [`is_complete`](#is_complete), which is a read-only probe.
All synchronization is built from three cache-line-aligned atomics and no mutexes: one compare-and-swap per dispatch, one `fetch_sub` per contributor, and plain loads and stores:

- `epoch_` - the generation clock: __odd__ while a fork is in flight, __even__ when idle; stepped once by the dispatcher on dispatch and once by the last contributor on completion, so every generation advances it by exactly two, each step a store by the clock's only writer;
- `threads_to_sync_` - the countdown identifying the __last__ contributor - the only thread allowed to make the completion step; zero exactly when no contributor runs, so a dispatch claims the pool by exchanging it from zero;
- `mood_` - the lifecycle switch between spinning, sleeping, and exiting workers.
Four synchronization edges keep the non-atomic fork state safe:

- __claim:__ the dispatcher exchanges the countdown from zero with an acquire, so the fork state it writes next follows every read of the previous one, then waits out the previous completion step, so a dispatch never races it and generation tokens are always odd;
- __publish:__ the dispatcher releases the dispatch step; contributors acquire it and see the fork state and the countdown;
- __completion__ __chain:__ every contributor decrements the countdown with `acq_rel`, chaining each contributor's writes into the last one;
- __completion__ __edge:__ the last contributor releases the completion step, so any acquire-load observing it sees __all__ contributors' results - [`is_complete`](#is_complete) included.
On [`caller_inclusive_k`](#caller_inclusive_k) pools the calling thread owes a slice that only runs inside [`unsafe_join`](#unsafe_join), so [`is_complete`](#is_complete) stays `false` until then: the poll-then-join pattern is reserved for [`caller_exclusive_k`](#caller_exclusive_k) pools.

See also: [https://ashvardanian.com/posts/beyond-openmp-in-cpp-rust/#four-horsemen-of-performance](https://ashvardanian.com/posts/beyond-openmp-in-cpp-rust/#four-horsemen-of-performance)

See also: [https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering](https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering)

See also: [https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2055r0.pdf](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2055r0.pdf)

## allocator_t

Allocator the pool rebinds to its worker cells - the pool's one allocation.

```cpp
using allocator_t = allocator_type_;
```

## micro_yield_t

Functor spinning threads while they wait on an atomic.

```cpp
using micro_yield_t = micro_yield_type_;
```

## atomic_ref

Reference reading and writing the words the pool synchronizes through.

```cpp
template <typename value_type_>
using atomic_ref = atomic_ref_template_<value_type_>;
```

## cache_hints_t

Functor demoting and promoting individual cache lines.

```cpp
using cache_hints_t = cache_hints_type_;
```

## index_t

Unsigned counter width; narrow it below `std::size_t` only to debug wrap-around.

```cpp
using index_t = index_type_;
```

## epoch_index_t

Number of previous API calls, in [0, UINT_MAX).

```cpp
using epoch_index_t = index_t;
```

Notes: At the `fu8_t` and `fu16_t` debug widths a worker stalled across exactly `2^bits` epochs would alias its `last_epoch` - astronomically unlikely at `size_t`.

## generation_t

Token returned from [`unsafe_for_threads`](#unsafe_for_threads); always odd, or zero for a refused dispatch.

```cpp
using generation_t = epoch_index_t;
```

## thread_index_t

Core index, or thread ID, in [0, threads_count).

```cpp
using thread_index_t = index_t;
```

## compute_domain_index_t

Dense index in [0, compute_domains_count).

```cpp
using compute_domain_index_t = index_t;
```

## indexed_split_t

Half-open range of tasks handed to one thread.

```cpp
using indexed_split_t = indexed_split<index_t>;
```

## thread_in_domain_t

One thread paired with the compute domain it runs on.

```cpp
using thread_in_domain_t = thread_in_domain<index_t>;
```

## claim_t

One private cursor per thread.

```cpp
using claim_t = dynamic_claim<index_t>;
```

## worker_cell_allocator_t

The pool's allocator rebound to the padded per-thread cells.

```cpp
using worker_cell_allocator_t = typename std::allocator_traits<allocator_t>::template rebind_alloc<worker_cell_t>;
```

## worker_cells_t

The pool's single buffer: one worker cell per [`alignment_k`](#alignment_k) stride.

```cpp
using worker_cells_t = dynamic_padded_array<worker_cell_t, worker_cell_allocator_t>;
```

## punned_fork_context_t

Pointer to the on-stack fork lambda.

```cpp
using punned_fork_context_t = void *;
```

## trampoline_t

Wraps the lambda's `operator()`.

```cpp
using trampoline_t = void(*)(punned_fork_context_t, thread_index_t);
```

## kind_k

Tags this pool as the domain-agnostic shape.

```cpp
pool_kind_t kind_k;
```

## alignment_k

Alignment isolating the pool's atomics onto their own cache lines.

```cpp
std::size_t alignment_k;
```

## flat_pool

```cpp
flat_pool(flat_pool &&)=delete;
```

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

```cpp
flat_pool(allocator_t const & alloc = {}) noexcept;
```

## operator=

```cpp
flat_pool & operator=(flat_pool &&)=delete;
```

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

## ~flat_pool

```cpp
~flat_pool() noexcept;
```

## memory_usage

Estimates the amount of memory managed by this pool handle and internal structures.

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

Notes: This API is __not__ synchronized.

## is_lock_free

Checks if the thread-pool's core synchronization points are lock-free.

```cpp
bool is_lock_free() const noexcept;
```

## memory_domain_id

Returns the memory domain this thread-pool is pinned to.

```cpp
memory_domain_id_t memory_domain_id() const noexcept;
```

## first_thread

Returns the first thread index in the thread-pool.

```cpp
thread_index_t first_thread() const noexcept;
```

```cpp
thread_index_t first_thread(
    index_t compute_domain_index) const noexcept;
```

Returns the first global thread index of a __compute_domain__.

## unsafe_dynamic_claim_ref

Exposes a thread's private claim cursor.

```cpp
claim_t & unsafe_dynamic_claim_ref(
    thread_index_t const thread) noexcept;
```

Use with caution.

## threads_count

Returns the number of threads in the thread-pool, including the main thread.

```cpp
thread_index_t threads_count() const noexcept;
```

```cpp
thread_index_t threads_count(
    index_t compute_domain_index) const noexcept;
```

Returns the number of threads in one NUMA-specific local __compute_domain__.

Notes: This API is __not__ synchronized.

Notes: Shape parity with `distributed_pool:` generic callers - the C ABI's `visit` and the distributed invokers - call `pool.threads_count(domain)` on every pool kind.

## caller_exclusivity

Reports if the current calling thread will be used for broadcasts.

```cpp
caller_exclusivity_t caller_exclusivity() const noexcept;
```

Notes: This API is __not__ synchronized.

## spawn

Creates a thread-pool with the given number of threads.

```cpp
status_t spawn(
    thread_index_t const threads,
    caller_exclusivity_t const exclusivity = caller_inclusive_k) noexcept;
```

- `threads` (in): The number of threads to be used.
- `exclusivity` (in): Should we count the calling thread as one of the threads?

Notes: This is the de-facto __constructor__ - you only call it again after [`terminate`](#terminate).

Statuses: - [`invalid_argument_k`](#invalid_argument_k) Zero `threads`.
- [`already_spawned_k`](#already_spawned_k) The pool still holds workers.
- [`unsupported_k`](#unsupported_k) A caller-exclusive or multi-thread pool without OS threads.

## for_threads

Executes a `fork` function in parallel on all threads.

```cpp
template <is_thread_callback<index_t> fork_type_>
broadcast_join<flat_pool, fork_type_> for_threads(
    fork_type_ && fork) noexcept;
```

- `fork` (in): The callback object, receiving the thread index as an argument.

Notes: Even in the [`caller_exclusive_k`](#caller_exclusive_k) mode, can be called from just one thread!

See also: For advanced resource management, consider `[unsafe_for_threads](#unsafe_for_threads)` and `[unsafe_join](#unsafe_join)`.

## terminate

Stops all threads and deallocates the thread-pool after the last call finishes.

```cpp
void terminate() noexcept;
```

When and how __not__ to use this function:

- as a synchronization point between concurrent tasks.When and how to use this function:

- as a de-facto __destructor__, to stop all threads and deallocate the pool.
- when you want to __restart__ with a different number of threads.

Notes: Can be called from __any__ thread, after the last dispatch was joined.

Notes: Must [`spawn`](#spawn) again to re-use the pool.

## nap

Parks "workers" on the waiter's monitor until the next dispatch or [`terminate`](#terminate).

```cpp
void nap() noexcept;
```

Wakes without a syscall. Waiters with no monitor keep spinning on their hint.

Notes: Callable only __between__ tasks on a single thread; no synchronization runs.

See also: [sleep](#sleep) for long idle periods, where the kernel takes the core back.

## sleep

Blocks "workers" in the kernel until the next dispatch or [`terminate`](#terminate).

```cpp
void sleep(std::size_t const wake_up_periodicity_micros = 0) noexcept;
```

Useful in batch processing when we clearly know the next task won't arrive for a while, so power can be saved at the price of syscalls in that dispatch. Falls back to [`nap`](#nap) where the kernel can't wait on a word.

It may also be used in a high-level Python or JavaScript library offloading some parallel operations to an underlying C++ engine, where latency is irrelevant.

- `wake_up_periodicity_micros` (in): Longest a worker stays blocked before re-checking, or 0 to leave the wake to the dispatch alone.

Notes: Callable only __between__ tasks on a single thread; no synchronization runs.

## for_slices

Distributes `n` similar-duration calls between threads in slices, not per index.

```cpp
template <is_slice_callback<index_t> fork_type_ = dummy_lambda_t>
broadcast_join<flat_pool, invoke_for_slices<fork_type_, index_t>> for_slices(
    index_t const n,
    fork_type_ && fork) noexcept;
```

- `n` (in): The total length of the range to split between threads.
- `fork` (in): The callback, receiving a __tasks_range_t__ and a __[thread_in_domain_t](#thread_in_domain_t)__.

## for_n

Distributes `n` similar duration calls between threads.

```cpp
template <is_task_callback<index_t> fork_type_ = dummy_lambda_t>
broadcast_join<flat_pool, invoke_for_n<fork_type_, index_t>> for_n(
    index_t const n,
    fork_type_ && fork) noexcept;
```

Designed for a "balanced" workload where all threads do roughly equal work, and `fork` runs `n` times, with each thread receiving a slice of consecutive tasks.

- `n` (in): The number of times to call the `fork`.
- `fork` (in): The callback object, receiving a task index and a __[thread_in_domain_t](#thread_in_domain_t)__.

See also: [for_n_dynamic](#for_n_dynamic) for a more dynamic workload.

See also: [for_slices](#for_slices) if you prefer to receive workload slices over individual indices.

## for_n_dynamic

Executes uneven tasks on all threads, greedying for work.

```cpp
template <is_task_callback<index_t> fork_type_ = dummy_lambda_t>
broadcast_join<flat_pool, invoke_for_n_dynamic<flat_pool, fork_type_, index_t>> for_n_dynamic(
    index_t const n,
    fork_type_ && fork) noexcept;
```

- `n` (in): The number of times to call the `fork`.
- `fork` (in): The callback object, receiving a task index and a __[thread_in_domain_t](#thread_in_domain_t)__.

See also: [for_n](#for_n) for a more "balanced" evenly-splittable workload.

## unsafe_for_threads

Executes a `fork` function in parallel on all threads, not waiting for the result.

```cpp
template <is_thread_callback<index_t> fork_type_>
generation_t unsafe_for_threads(fork_type_ & fork) noexcept;
```

- `fork` (in): The callback __reference__, receiving the thread index as an argument.

See also: Use in conjunction with `[unsafe_join](#unsafe_join)`.

## is_complete

Returns true if the generation identified by [`generation`](#generation) has completed.

```cpp
bool is_complete(generation_t generation) const noexcept;
```

On [`caller_inclusive_k`](#caller_inclusive_k) pools this only turns `true` once [`unsafe_join`](#unsafe_join) runs the caller's slice, so poll-then-join applies only to [`caller_exclusive_k`](#caller_exclusive_k) pools.

Notes: A `true` result synchronizes with all contributors: their writes are visible.

## unsafe_join

Blocks the calling thread until the generation identified by [`generation`](#generation) finishes.

```cpp
void unsafe_join(generation_t generation) noexcept;
```

```cpp
void unsafe_join() noexcept;
```

Blocks the calling thread until the currently broadcasted task finishes.

Idempotent, returning immediately for already-joined or stale generations.

Notes: On [`caller_inclusive_k`](#caller_inclusive_k) pools, first executes the calling thread's slice.

## compute_domains_count

Number of individual sub-pools with the same NUMA-locality and QoS.

```cpp
index_t compute_domains_count() const noexcept;
```

## compute_domain_index_of

The topology's compute domain behind our only one, so the identity.

```cpp
index_t compute_domain_index_of(
    index_t compute_domain_index) const noexcept;
```

## thread_local_index

Converts a `global_thread_index` to a local thread index within a __compute_domain__.

```cpp
thread_index_t thread_local_index(
    thread_index_t global_thread_index,
    index_t compute_domain_index) const noexcept;
```

## make_mutex

Helper function to create a spin mutex with same yield characteristics.

```cpp
spin_mutex<micro_yield_t, alignment_k> make_mutex() noexcept;
```

## worker_cell_t

Everything the pool keeps __per__ __thread__, on a cache line of its own.

```cpp
struct worker_cell_t
```

The claim cursor must not share a line with anything, or the dynamic scheduler reintroduces the very coherence traffic that giving each thread a private cursor exists to remove. Rather than allocate a second array beside `std::thread`, both live in one padded cell, so the pool still performs exactly one allocation - in [`spawn`](#spawn), never on a dispatch path.

Cells are indexed by __thread__ __index__, so on inclusive pools cell 0 belongs to the caller and holds no `std::thread`, costing a cell but giving `claim` and `worker` one index.

- `claim` `claim_t`: This thread's private [dynamic_claim](#dynamic_claim) cursor for [`for_n_dynamic`](#for_n_dynamic).
- `worker` `std::thread`: The worker thread; default-constructed, and left so for the caller's own cell.

Notes: Separation comes from the buffer's __stride__, not from an `alignas` on this type. A `std::allocator` only promises `__STDCPP_DEFAULT_NEW_ALIGNMENT__`, so over-aligning the cell would placement-new it into storage that cannot satisfy the request.

## broadcast_join

A synchronization point that waits for all threads to finish the last fork.

```cpp
template <typename pool_type_,
          typename fork_type_>
struct broadcast_join
```

The fork is dispatched at __construction__, so the workers start immediately and the caller may overlap its own work. The `join` call, or the destructor, waits for completion, and on [`caller_inclusive_k`](#caller_inclusive_k) pools first runs the calling thread's own slice, so only [`caller_exclusive_k`](#caller_exclusive_k) pools may poll [`is_complete`](#is_complete) before it.

The pool admits one dispatch at a time, so it refuses this one while another runs: another caller's, or the one whose fork this broadcast is created in. A refused broadcast runs nothing, joins nothing, keeps [`generation`](#generation) at zero, and is never retried, not even by the destructor.

You don't have to explicitly handle the return value and wait on it. According to the C++ standard, the destructor of the `[broadcast_join](#broadcast_join)` will be called in the end of the `for_threads-calling` expression.

The object is immovable: the pool holds a pointer to the `fork_` member for the lifetime of the broadcast, so the object must never relocate. Guaranteed C++17 copy elision still allows returning it by value from [`for_threads`](#for_threads).

Notes: You don't have to explicitly call any of the APIs, it's like `std::jthread` ;)

## broadcast_join

```cpp
broadcast_join(pool_t & pool_ref, fork_t && f) noexcept;
```

```cpp
broadcast_join(broadcast_join &&)=delete;
```

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

## fork_ref

The wrapped fork; only read it after `join`.

```cpp
fork_t & fork_ref() noexcept;
```

## generation

The generation token of this broadcast; odd once dispatched, zero once refused.

```cpp
generation_t generation() const noexcept;
```

## is_complete

Non-blocking check; can only turn `true` before `join` on caller-exclusive pools.

```cpp
bool is_complete() const noexcept;
```

## ~broadcast_join

```cpp
~broadcast_join() noexcept;
```

## operator=

```cpp
broadcast_join & operator=(broadcast_join &&)=delete;
```

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

## succeeded

Whether `status` reports success.

```cpp
bool succeeded(status_t status) noexcept;
```

## failed

Whether `status` reports failure.

```cpp
bool failed(status_t status) noexcept;
```

## status_to_string

Static, English description of `status`.

```cpp
char const * status_to_string(status_t status) noexcept;
```

Never returns `nullptr`.

Notes: The fall-through sits after the `switch` rather than in a `default:` label, so adding an enumerator without a text here is a `-Wswitch` warning instead of silent prose.

## capability_name

Lower-case name of one capability bit, or `nullptr` for composite and unknown ones.

```cpp
char const * capability_name(capabilities_t const capability) noexcept;
```

Notes: The one source of truth for capability names: the string builders and the C ABI both defer here.

See also: [capability_named](#capability_named), its inverse.

## capability_named

The single capability bit named `name`, or [`capabilities_unknown_k`](#capabilities_unknown_k) if none matches.

```cpp
capabilities_t capability_named(char const *name) noexcept;
```

Notes: The inverse of [`capability_name`](#capability_name); it defers to that one table, so the two cannot drift.

## bytes_for_elements

Bytes occupied by `count` elements of `element_bytes` each.

```cpp
std::size_t bytes_for_elements(
    std::size_t count,
    std::size_t element_bytes) noexcept;
```

## copy_bytes

Byte-wise `memcpy` of `from` into `to`, written as an explicit loop.

```cpp
template <typename value_type_>
void copy_bytes(value_type_ const *from, value_type_ *to) noexcept;
```

Not `std::memcpy:` the wait monitors that call this - `[arm64_wfet_t](https://www.unum.cloud/docs/forkunion/cpp/busy-waiting.md#arm64_wfet_t)` and `[riscv64_wrs_t](https://www.unum.cloud/docs/forkunion/cpp/busy-waiting.md#riscv64_wrs_t)` - are pinned to a narrower `target(...)` than `-march=native`, where the fortified `always_inline` `memcpy` cannot inline - "target specific option mismatch". Reading and writing through `unsigned char` keeps it well-defined.

## min_of

The smaller of `first` and `second`, without `<algorithm>` or the `windows.h` `min` macro.

```cpp
template <typename value_type_>
value_type_ min_of(value_type_ first, value_type_ second) noexcept;
```

## max_of

The larger of `first` and `second`, the `max` twin of [min_of](#min_of).

```cpp
template <typename value_type_>
value_type_ max_of(value_type_ first, value_type_ second) noexcept;
```

## div_ceil

Ceiling of `value` divided by `divisor` - how many buckets of that size a value needs.

```cpp
std::size_t div_ceil(std::size_t value, std::size_t divisor) noexcept;
```

Notes: `divisor` must be non-zero; overflow of `value` near the type maximum is not guarded.

## round_up_to_multiple

Smallest multiple of `multiple` that is not less than `value`.

```cpp
std::size_t round_up_to_multiple(
    std::size_t value,
    std::size_t multiple) noexcept;
```

Notes: `multiple` must be non-zero; overflow of `value` near the type maximum is not guarded.

## round_up_to_pow2

Smallest power of two that is not less than `value`; one for zero.

```cpp
std::size_t round_up_to_pow2(std::size_t value) noexcept;
```

## split_mix

The SplitMix64 avalanche - a well-mixed pure function of the `counter`.

```cpp
std::uint64_t split_mix(std::uint64_t const counter) noexcept;
```

A counter-based generator instead of a stateful one: every draw is independent, so parallel consumers need no shared state, and a sequence is reproducible from indices alone. The same constants drive the benchmark generators in `bench/`, ported bit-identically to Rust and Zig.

## dense_rank

Dense-ranks `count` items by an ascending integer key, writing each item's 0-based rank.

```cpp
template <typename key_type_, typename assign_type_>
std::size_t dense_rank(
    std::size_t count,
    key_type_ const & key,
    assign_type_ const & assign) noexcept;
```

`key(index)` must read a __stable__ source and `assign(index, rank)` write a __different__ field, so ranking in place never corrupts a not-yet-ranked item whose key repeats. Used to turn raw CPU capacities into compute levels and raw memory tiers into memory levels.

## drain_claim

Drains whatever is left of one thread's slice into `fork` - the shared core of both the flat and the distributed [`for_n_dynamic`](#for_n_dynamic) invokers, so its invariants live in one place.

```cpp
template <typename index_type_, typename locator_type_, typename fork_type_, typename cache_hints_type_>
void drain_claim(
    dynamic_claim<index_type_> & claim,
    locator_type_ const & at,
    fork_type_ & fork,
    cache_hints_type_ cache_hints) noexcept;
```

```cpp
template <typename pool_type_, typename index_type_, typename locator_type_, typename fork_type_>
void drain_claim(
    pool_type_ & pool,
    index_type_ const slice,
    locator_type_ const & at,
    fork_type_ & fork) noexcept;
```

Drains what is left of the `slice` thread's claim in `pool`, whether or not we own it.

A read-only probe first: a drained slice is skipped in Shared state - no dirtying add, no line migration - which is the entire cost of visiting an empty neighbour once a small dispatch runs dry. A live slice is promoted with write intent while the probe's branch resolves, claimed one task at a time - the makespan guarantee of greedy list scheduling - and demoted once our overshooting add has dirtied it, so the next visitor snoops the LLC instead of this core.

See also: The `[dynamic_claim](#dynamic_claim)` overload above, where the probe and the overshoot invariants live.

## cpu_capabilities

The instruction-level bits this CPU and its kernel offer: the spin hint, the monitored wait, the cache-line hint and the atomics of the ISA in hand.

```cpp
capabilities_t cpu_capabilities() noexcept;
```

See also: [ram_capabilities](#ram_capabilities) for the memory side; together they form `[runtime_capabilities](#runtime_capabilities)`.

## ram_capabilities

The memory-placement facilities this machine offers - NUMA and huge/large pages.

```cpp
capabilities_t ram_capabilities() noexcept;
```

See also: [cpu_capabilities](#cpu_capabilities) for the busy-wait side; together they form `[runtime_capabilities](#runtime_capabilities)`.

## runtime_capabilities

Which features this machine turned out to offer, probing the CPU and the memory system.

```cpp
capabilities_t runtime_capabilities() noexcept;
```

See also: [comptime_capabilities](#comptime_capabilities) for what this build is able to ask for in the first place.

## comptime_capabilities

Which kernel facilities this unit was built to use, one bit per `FORKUNION_WITH_*`.

```cpp
capabilities_t comptime_capabilities() noexcept;
```

Consult it before reaching for a domain-aware API. Without [`capability_colocate_pools_on_domain_k`](#capability_colocate_pools_on_domain_k) there is no `colocated_pool` to spawn and no `[linux_numa_allocator](https://www.unum.cloud/docs/forkunion/cpp/numa.md#linux_numa_allocator)` to construct, and a caller has no other way to tell that apart from a machine that merely has one compute domain.

These are the __same__ facility bits [`runtime_capabilities`](#runtime_capabilities) reports, asked the other way: this says the code was compiled, that says the machine offers it, and a facility is usable only where both agree.

See also: [runtime_capabilities](#runtime_capabilities) for what the machine underneath turned out to offer.

## default_alignment_k

Defines variable alignment to avoid false sharing.

```cpp
std::size_t default_alignment_k;
```

The C++ STL way to do it is to use `std::hardware_destructive_interference_size` if available:

```
#if defined(__cpp_lib_hardware_interference_size)
static constexpr std::size_t default_alignment_k = std::hardware_destructive_interference_size;
#else
static constexpr std::size_t default_alignment_k = alignof(std::max_align_t);
#endif
```

That however results into all kinds of ABI warnings with GCC, and suboptimal alignment choice, unless you hard-code `–param hardware_destructive_interference_size=64` or disable the warning with `-Wno-interference-size`.

So the width is `FORKUNION_DEFAULT_ALIGNMENT`, picked from the target. Only `alignas` rests on this guess; a per-thread stride takes `destructive_interference_bytes()`, which asks the machine.

See also: [https://en.cppreference.com/w/cpp/thread/hardware_destructive_interference_size](https://en.cppreference.com/w/cpp/thread/hardware_destructive_interference_size)

See also: [https://docs.rs/crossbeam-utils/latest/crossbeam_utils/struct.CachePadded.html](https://docs.rs/crossbeam-utils/latest/crossbeam_utils/struct.CachePadded.html)

## is_dynamic_invoker

Whether `fork_type_` is one of our [`for_n_dynamic`](#for_n_dynamic) invokers, whose cursors the pool rewinds right after admitting the dispatch.

```cpp
bool is_dynamic_invoker;
```

The cursors live in the pool, shared by every dispatch, so a refused one must not touch them. Matched by type rather than by a member name, so no user fork is ever asked to rewind.

