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
Improve: Steer Apple efficiency-core pools via QoS
A colocated_pool spawned on an efficiency domain now asks for
QOS_CLASS_UTILITY - Darwin's only lever that actually places threads on
E-cores. The dense compute_level cannot make that call alone: the bottom
rank is E-cores on an A18 but big cores on an M5 Pro, so the harvest now
keeps each perflevel's absolute class, parsed from hw.perflevelN.name
into the new apple_core_quality field. The whole kit - parser, field,
and consumer - is gated on FU_WITH_PLACE_THREADS_BY_CORE_CLASS, so
turning the capability off erases it together.

The iOS CI job now also links and runs the test suite on a booted
simulator across an sdk x std matrix, instead of stopping at the
device-SDK compile.
  • Loading branch information
ashvardanian committed Jul 14, 2026
commit c7be3fb3f2bde187a9fbd53c0a3f115dcebd2933
35 changes: 24 additions & 11 deletions .github/workflows/prerelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -644,26 +644,39 @@ jobs:
- name: Test Zig (portable)
run: zig build test -Dportable=true

# iOS shares Darwin's `pthread_attr_set_qos_class_np` and `hw.perflevel*`, and shares its refusal
# to pin. Compiling for the device and for the simulator is enough to keep those paths honest;
# running is not, because a simulator binary reads the *host* Mac's topology, not a phone's.
# iOS shares Darwin's QoS scheduling and `hw.perflevel*` topology, and shares its refusal to pin.
# The device SDK is compile-only - a phone can't run on the CI host - but the simulator binary is
# linked and run on a booted simulator. A simulator reads the *host* Mac's topology, so this proves
# the Apple path under the iOS ABI, not any particular phone's layout.
build_ios:
name: iOS (Compile Only)
name: iOS (${{ matrix.sdk }}, ${{ matrix.std }})
runs-on: macos-15
strategy:
fail-fast: false
matrix:
sdk: [iphoneos, iphonesimulator]
std: [c++17, c++20]
include:
- { sdk: iphoneos, triple: arm64-apple-ios17.0 }
- { sdk: iphonesimulator, triple: arm64-apple-ios17.0-simulator }
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Build C++ for ${{ matrix.sdk }}
- name: Compile the C++ test and the C shim
run: |
SYSROOT=$(xcrun --sdk ${{ matrix.sdk }} --show-sdk-path)
if [ "${{ matrix.sdk }}" = "iphoneos" ]; then TARGET=arm64-apple-ios17.0; \
else TARGET=arm64-apple-ios17.0-simulator; fi
clang++ -std=c++20 -O2 -Wall -Wextra -Werror -target $TARGET -isysroot "$SYSROOT" \
-Iinclude -c scripts/test.cpp -o test_ios.o
clang++ -std=c++17 -O2 -Wall -Wextra -Werror -target $TARGET -isysroot "$SYSROOT" \
-Iinclude -c c/forkunion.cpp -o forkunion_ios.o
clang++ -std=${{ matrix.std }} -O2 -Wall -Wextra -Werror -target ${{ matrix.triple }} \
-isysroot "$SYSROOT" -Iinclude scripts/test.cpp -o forkunion_test_ios
clang++ -std=${{ matrix.std }} -O2 -Wall -Wextra -Werror -target ${{ matrix.triple }} \
-isysroot "$SYSROOT" -Iinclude -c c/forkunion.cpp -o forkunion_ios.o

# GitHub's macOS images pre-create simulators for their Xcode; boot the first iPhone, don't mint one.
- name: Run the C++ test on the iOS Simulator
if: matrix.sdk == 'iphonesimulator'
run: |
DEVICE_ID=$(xcrun simctl list devices available iPhone \
| grep -oE "[0-9A-F-]{36}" | head -1)
test -n "$DEVICE_ID"
xcrun simctl boot "$DEVICE_ID"
xcrun simctl spawn "$DEVICE_ID" "$PWD/forkunion_test_ios"
43 changes: 25 additions & 18 deletions include/forkunion/distributed.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,11 @@ struct alignas(default_alignment_k) pinned_thread_t {
core_id_t core_id {-1};
/** @brief Thread name, written by the spawner and applied by the worker to itself. */
char name[16] {};
/** @brief QoS class of this worker's core. @todo Populate from VFS, if available. */
core_quality_t core_quality {-1};
#if FU_WITH_PLACE_THREADS_BY_CORE_CLASS
/** @brief Apple's absolute class for this worker's domain, or -1 when unnamed.
* @sa `compute_domain_t::apple_core_quality`, copied here at spawn. */
core_quality_t apple_core_quality {-1};
#endif
/**
* @brief This thread's private cursor for `for_n_dynamic`. @sa `dynamic_claim`.
* @note Lives here, rather than in a second array, so the pool allocates once and the cursor
Expand Down Expand Up @@ -365,6 +368,9 @@ struct colocated_pool {
pthreads_[0].handle.store(::pthread_self(), std::memory_order_release);
#endif
pthreads_[0].id.store(current_thread_id(), std::memory_order_release);
#if FU_WITH_PLACE_THREADS_BY_CORE_CLASS
pthreads_[0].apple_core_quality = domain.apple_core_quality;
#endif
}

// The startup sequence for the POSIX threads differs from the `flat_pool`,
Expand Down Expand Up @@ -396,14 +402,17 @@ struct colocated_pool {
// Apple offers no pinning; a Quality-of-Service class is the whole placement story, and
// it must be chosen before the thread exists. On a chip with efficiency cores, `UTILITY`
// is what confines a thread to them; on an all-performance chip the class is inert.
::pthread_attr_set_qos_class_np(&attributes, _qos_for_level(domain.compute_level, compute_levels), 0);
::pthread_attr_set_qos_class_np(&attributes, _qos_for_domain(domain, compute_levels), 0);
#endif
created = ::pthread_create(&new_pthread_handle, &attributes, &_posix_worker_loop, this) == 0;
::pthread_attr_destroy(&attributes);
pthreads_[i].handle.store(new_pthread_handle, std::memory_order_relaxed);
pthreads_[i].id.store(0, std::memory_order_relaxed); // ? 0 means "not published yet"
#endif
pthreads_[i].core_id = -1; // ? Not pinned yet
#if FU_WITH_PLACE_THREADS_BY_CORE_CLASS
pthreads_[i].apple_core_quality = domain.apple_core_quality;
#endif

if (!created) {
mood_.store(mood_t::die_k, std::memory_order_release);
Expand Down Expand Up @@ -876,24 +885,22 @@ struct colocated_pool {

#if FU_WITH_PLACE_THREADS_BY_CORE_CLASS
/**
* @brief Maps a compute level onto the only placement control Darwin offers.
* @param[in] level The domain's `compute_level`, where higher is more performant.
* @brief Maps a compute domain onto the only placement control Darwin offers: a QoS class.
* @param[in] levels Distinct levels the machine reports; 1 means every core is interchangeable.
*
* The top tier asks for `USER_INITIATED`, not `USER_INTERACTIVE`: the latter is reserved for work
* a person is waiting on, and a compute pool is not that. Everything below it takes `DEFAULT`.
*
* @note It is tempting to give the bottom tier `QOS_CLASS_UTILITY`, which is what confines a
* thread to efficiency cores. That is wrong here. `compute_level` is a dense rank over
* `hw.perflevelN`, and the bottom rank is only an @b efficiency tier on the chips that have
* one. An M5 Pro reports two levels named "Super" and "Performance", both big cores - and
* `UTILITY` would deprioritize two thirds of the machine. Telling the two cases apart needs
* `hw.perflevelN.name`, which the harvest does not yet keep.
* The fastest tier present runs the hot path at `USER_INITIATED` - not `USER_INTERACTIVE`, which
* is reserved for work a person is waiting on. Below it, only a tier the OS names "Efficiency"
* takes `UTILITY`, the class that confines threads to E-cores. The rank alone cannot decide that:
* the bottom rank is E-cores on an A18 but big cores on an M5 Pro, where `UTILITY` would banish
* two thirds of the machine. Checking rank first keeps an all-efficiency chip honest - its E-cores
* are the fastest thing present. A hidden or unknown name parses to `apple_performance_k`, so the
* fallback is rank-only and `UTILITY` is never guessed.
*/
static qos_class_t _qos_for_level(std::size_t const level, std::size_t const levels) noexcept {
if (levels <= 1) return QOS_CLASS_USER_INITIATED;
if (level + 1 == levels) return QOS_CLASS_USER_INITIATED; // ? The fastest tier this chip has
return QOS_CLASS_DEFAULT;
static qos_class_t _qos_for_domain(compute_domain_t const &domain, std::size_t const levels) noexcept {
if (domain.compute_level + 1 >= levels) return QOS_CLASS_USER_INITIATED; // ? The fastest tier present
if (domain.apple_core_quality == apple_efficiency_k)
return QOS_CLASS_UTILITY; // ? Genuine E-cores - confine here
return QOS_CLASS_DEFAULT; // ? A slower big tier stays big
}
#endif

Expand Down
78 changes: 77 additions & 1 deletion include/forkunion/topology.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,16 @@ struct compute_domain_t {
memory_domain_index_t memory_domain_index {};
/** @brief QoS ordinal, sorted least-to-most performant. */
std::size_t compute_level {0};
#if FU_WITH_PLACE_THREADS_BY_CORE_CLASS
/**
* @brief Apple's absolute class for these cores, from `hw.perflevelN.name`; -1 when unnamed.
* @sa `colocated_pool`'s `_qos_for_domain`, for why the relative rank above cannot replace it.
*
* Spawn-path plumbing, not a second caller-facing rank: like `first_core_id`, it is the raw
* material the platform's placement call consumes - an affinity mask there, a QoS class here.
*/
core_quality_t apple_core_quality {-1};
#endif
/**
* @brief Relative throughput of @b one core here; 0 when the platform exposes no rating.
* @sa `compute_level` is a dense ordinal for grouping - never divide by it.
Expand Down Expand Up @@ -1038,7 +1048,61 @@ FU_MAYBE_UNUSED_ static inline std::size_t apple_sysctl_uint(char const *name) n
if (::sysctlbyname(name, &value, &length, nullptr, 0) != 0) return 0;
return static_cast<std::size_t>(value);
}
#endif
#endif // FU_ON_APPLE

/* The core-quality kit exists for one consumer - the QoS class a `colocated_pool` assigns at spawn -
* so it is gated on that capability: turning it off erases producer, field, and consumer together. */
#if FU_WITH_PLACE_THREADS_BY_CORE_CLASS
/** @brief Reads a string `sysctl` by name into @p out (always NUL-terminated), returning success. */
FU_MAYBE_UNUSED_ static inline bool apple_sysctl_string(char const *name, char *out, std::size_t cap) noexcept {
if (cap == 0) return false;
std::size_t length = cap;
if (::sysctlbyname(name, out, &length, nullptr, 0) != 0) {
out[0] = '\0';
return false;
}
// ? A value that exactly filled the buffer arrives unterminated
out[cap - 1] = '\0';
return true;
}

/**
* @brief Apple's `hw.perflevelN.name` vocabulary as an @b absolute ladder; higher is more performant.
* @note Absolute, unlike `compute_level`: "Performance" is the same class on an M1 (its top tier)
* and an M5 Pro (its bottom). Apple has shipped exactly these three names.
*/
enum apple_core_quality_t : core_quality_t {
apple_efficiency_k = 0, // "Efficiency" - the only tier that is physically E-cores
apple_performance_k = 1, // "Performance"
apple_super_k = 2, // "Super" - the M5-era top tier, still a big core
};

/**
* @brief The name Apple gives an absolute core class, or `nullptr` for an unknown one.
* @note The one source of truth for the names; `apple_core_quality_from_name` inverts it.
*/
FU_MAYBE_UNUSED_ static inline char const *apple_core_quality_name(core_quality_t const quality) noexcept {
switch (quality) {
case apple_efficiency_k: return "Efficiency";
case apple_performance_k: return "Performance";
case apple_super_k: return "Super";
default: return nullptr;
}
}

/**
* @brief Maps a `hw.perflevelN.name` to its absolute class; inverts `apple_core_quality_name`.
* @retval apple_performance_k for a null or unrecognised name, so `UTILITY` is never guessed.
*/
FU_MAYBE_UNUSED_ static inline core_quality_t apple_core_quality_from_name(char const *name) noexcept {
if (name == nullptr) return apple_performance_k;
for (core_quality_t quality = apple_efficiency_k; quality <= apple_super_k; ++quality) {
char const *const candidate = apple_core_quality_name(quality);
if (candidate != nullptr && std::strcmp(candidate, name) == 0) return quality;
}
return apple_performance_k;
}
#endif // FU_WITH_PLACE_THREADS_BY_CORE_CLASS

/**
* @brief NUMA topology descriptor: describing memory pools and core counts next to them.
Expand Down Expand Up @@ -1548,6 +1612,15 @@ struct machine_topology {
// ? A level with no `cpusperl2` is one undivided cluster, not zero-sized ones.
if (cores_per_cluster == 0 || cores_per_cluster > level_cores) cores_per_cluster = level_cores;

#if FU_WITH_PLACE_THREADS_BY_CORE_CLASS
// The absolute class the QoS choice keys on; a hidden name (a locked-down sandbox)
// parses to a big tier, never to efficiency cores.
std::snprintf(name, sizeof(name), "hw.perflevel%zu.name", level);
char level_name[32];
apple_sysctl_string(name, level_name, sizeof(level_name));
core_quality_t const level_quality = apple_core_quality_from_name(level_name);
#endif

std::size_t const level_rank = nonempty_levels - 1 - levels_written;
for (std::size_t cut = 0; cut < level_cores; cut += cores_per_cluster) {
std::size_t const cluster_cores = (level_cores - cut) < cores_per_cluster //
Expand All @@ -1557,6 +1630,9 @@ struct machine_topology {
domain.memory_domain_id = 0;
domain.memory_domain_index = static_cast<memory_domain_index_t>(0);
domain.compute_level = level_rank; // ? Sibling clusters share their level's rank
#if FU_WITH_PLACE_THREADS_BY_CORE_CLASS
domain.apple_core_quality = level_quality;
#endif
domain.capacity = 0;
domain.cache_bytes = level_cache_bytes; // ? L2 is private to the cluster, shared within it
domain.first_core_id = core_ids_ptr + core_offset + cut;
Expand Down
Loading