Skip to main content

subetha_core/
cpuid.rs

1//! Tiny CPUID feature-detection surface that adaptive primitives
2//! across the SubEtha workspace consult to gate
3//! hardware-acceleration paths.
4//!
5//! The substrate exposes:
6//!
7//! - [`has_waitpkg`] - is the WAITPKG ISA extension
8//!   (`UMONITOR` / `UMWAIT` / `TPAUSE`) available?
9//! - [`has_movdir64b`] - is the MOVDIR64B ISA extension (atomic
10//!   non-temporal 64-byte cache-line store) available?
11//!
12//! WAITPKG was introduced on Intel Tremont (2019) and Tiger Lake
13//! (2020) microarchitectures, and on AMD Zen 5 (2024). Hosts older
14//! than that need a `PAUSE`-spin fallback; primitives that wait on
15//! a cache line (e.g. `SharedDequeUrd` in `subetha-cxc`) pick the
16//! wait strategy at runtime by calling this function once and
17//! caching the result.
18//!
19//! MOVDIR64B was introduced on Intel Tremont (2019) and Tiger Lake
20//! (2020), and on AMD Zen 5 (2024). It is the atomic 64-byte
21//! non-temporal store; `SharedDequeUrd` uses it to publish a whole
22//! mailbox line in one transaction, eliminating the cross-CCX
23//! coherence-upgrade traffic that the byte-by-byte fallback path
24//! pays on hosts without it.
25//!
26//! Both flags are probed via CPUID leaf 7 sub-leaf 0
27//! (`Structured Extended Feature Flags`): ECX bit 5 = WAITPKG,
28//! ECX bit 28 = MOVDIR64B.
29
30#[cfg(target_arch = "x86_64")]
31use std::sync::OnceLock;
32
33/// Returns `true` when the CPU advertises the WAITPKG ISA extension
34/// (`UMONITOR` / `UMWAIT` / `TPAUSE`).
35///
36/// On x86_64 the result is cached after the first probe. On
37/// non-x86_64 targets this always returns `false`.
38#[cfg(target_arch = "x86_64")]
39pub fn has_waitpkg() -> bool {
40    *waitpkg_available()
41}
42
43#[cfg(not(target_arch = "x86_64"))]
44pub fn has_waitpkg() -> bool {
45    false
46}
47
48#[cfg(target_arch = "x86_64")]
49fn waitpkg_available() -> &'static bool {
50    static CACHE: OnceLock<bool> = OnceLock::new();
51    CACHE.get_or_init(|| {
52        use std::arch::x86_64::{__cpuid, __cpuid_count};
53        // Validate CPUID leaf 7 is implemented by reading the
54        // max-leaf id from leaf 0. Pre-Nehalem silicon may not
55        // implement leaf 7 at all. `__cpuid` and `__cpuid_count`
56        // are safe on the current x86_64 stable surface; the
57        // `#[cfg(target_arch = "x86_64")]` gate above guarantees
58        // the only required invariant.
59        let max_leaf = __cpuid(0).eax;
60        if max_leaf < 7 {
61            return false;
62        }
63        // CPUID leaf 7, sub-leaf 0, ECX bit 5 = WAITPKG.
64        let r = __cpuid_count(7, 0);
65        (r.ecx >> 5) & 1 == 1
66    })
67}
68
69/// Returns `true` when the CPU advertises the MOVDIR64B ISA
70/// extension (atomic non-temporal 64-byte cache-line store).
71///
72/// MOVDIR64B encodes as `66 0F 38 F8 /r` and writes a 64-byte-
73/// aligned source cache line to a 64-byte-aligned destination cache
74/// line in one atomic transaction that bypasses the writing core's
75/// L1d (Write-Combining store). It eliminates the RFO coherence
76/// upgrade that a byte-by-byte fallback path pays when the
77/// destination line is in a remote core's L1d in M-state.
78///
79/// Available on Intel Tremont (2019), Tiger Lake (2020) and later
80/// Intel cores, and on AMD Zen 5 (2024) and later AMD cores.
81///
82/// On x86_64 the result is cached after the first probe. On
83/// non-x86_64 targets this always returns `false`.
84#[cfg(target_arch = "x86_64")]
85pub fn has_movdir64b() -> bool {
86    *movdir64b_available()
87}
88
89#[cfg(not(target_arch = "x86_64"))]
90pub fn has_movdir64b() -> bool {
91    false
92}
93
94#[cfg(target_arch = "x86_64")]
95fn movdir64b_available() -> &'static bool {
96    static CACHE: OnceLock<bool> = OnceLock::new();
97    CACHE.get_or_init(|| {
98        use std::arch::x86_64::{__cpuid, __cpuid_count};
99        let max_leaf = __cpuid(0).eax;
100        if max_leaf < 7 {
101            return false;
102        }
103        // CPUID leaf 7, sub-leaf 0, ECX bit 28 = MOVDIR64B.
104        let r = __cpuid_count(7, 0);
105        (r.ecx >> 28) & 1 == 1
106    })
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn has_waitpkg_does_not_panic_and_caches() {
115        // Exercise the cached probe path twice; the actual answer
116        // depends on the host CPU and is not asserted. The second
117        // call hits the `OnceLock` cache.
118        let first = has_waitpkg();
119        let second = has_waitpkg();
120        assert_eq!(first, second, "has_waitpkg must be idempotent");
121    }
122
123    #[test]
124    fn has_movdir64b_does_not_panic_and_caches() {
125        let first = has_movdir64b();
126        let second = has_movdir64b();
127        assert_eq!(first, second, "has_movdir64b must be idempotent");
128    }
129}