1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
//! CPU support for SIMD features.
//!
//! This module defines a type-safe, zero-cost mechanism for feature testing.
//! Functions relying on particular SIMD features take a type-generic parameter
//! that implements [`FeatureSet`], and use `where` bounds to test that it has
//! certain features.
/// A CPU feature.
///
/// Features are organized into _generations_, such as [`sse::FeatureGroup`].
/// At one time, only one generation of extensions should be used -- they use
/// different instruction formats and mixing them degrades performance. The
/// encoding of generations into the type system prevents incompatible features
/// from being mixed accidentally.
///
/// [`sse::FeatureGroup`]: super::sse::FeatureGroup
pub trait Feature<G>: Sized {
/// Determine whether this feature is supported.
///
/// Given a [`RuntimeSupport`], which tracks which features the current CPU
/// supports, this function extracts the feature represented by this type.
/// If the CPU does not support this type, [`None`] is returned.
fn get_support(runtime: &RuntimeSupport) -> Option<Self>;
}
/// A set of CPU features.
///
/// Types that implement this trait contain a fixed set of features. Whether a
/// particular feature is contained can be tested using [`HasFeature`].
///
/// The standard implementation of this type is a heterogenous list, using the
/// [`unit`] type `()` for the empty list and the [`tuple`] type `(H, T)` for
/// prepending an element `H` to a list. The [`feature_set`] macro can be
/// used to construct such a list more easily.
///
/// [`feature_set`]: crate::intel::low::feature_set
pub trait FeatureSet<G>: Sized {
/// Determine whether this set of features is supported.
///
/// Given a [`RuntimeSupport`], which tracks which features the current CPU
/// supports, this function extracts the features represented by this type.
/// If the CPU does not support all these features, [`None`] is returned.
fn get_support(runtime: &RuntimeSupport) -> Option<Self>;
}
impl<G> FeatureSet<G> for () {
fn get_support(_: &RuntimeSupport) -> Option<Self> {
// The empty feature list is always supported.
Some(())
}
}
impl<G, H, T> FeatureSet<G> for (H, T)
where H: Feature<G>, T: FeatureSet<G> {
fn get_support(runtime: &RuntimeSupport) -> Option<Self> {
Some((H::get_support(runtime)?, T::get_support(runtime)?))
}
}
/// Whether a CPU feature set contains a particular feature.
///
/// # Safety
///
/// A type `T` can safely implement `HasFeature<G, F>`, for any `G`, and `F`, if
/// and only if the following condition holds:
///
/// - If a soundly constructed value of type `T` exists, then an instance of the
/// feature `F` exists, indicating that the current CPU implements it.
#[marker]
pub unsafe trait HasFeature<G, F: Feature<G>>: FeatureSet<G> {}
unsafe impl<G, H, T> HasFeature<G, H> for (H, T)
where H: Feature<G>, T: FeatureSet<G> {}
unsafe impl<G, F, H, T> HasFeature<G, F> for (H, T)
where F: Feature<G>, H: Feature<G>, T: HasFeature<G, F> {}
unsafe impl<G, F> HasFeature<G, F> for ()
where F: Feature<G> {}
/// Feature support information for the current CPU.
///
/// This structure contains parsed information from `CPUID` about what SIMD and
/// related features are supported by the current CPU. It is a read-only type,
/// offering no way to directly mutate the internal feature readings (so that
/// features are not misrepresented as being supported).
pub struct RuntimeSupport {
// The SSE generation of instructions.
sse: bool,
sse2: bool,
sse3: bool,
ssse3: bool,
sse4_1: bool,
sse4_2: bool,
// The AVX generation of instructions.
avx: bool,
f16c: bool,
fma: bool,
avx2: bool,
}
impl RuntimeSupport {
/// Detect feature support in the current CPU.
pub fn detect() -> Self {
use raw_cpuid::CpuId;
let cpuid = CpuId::new();
let feats = cpuid.get_feature_info();
let feats = feats.as_ref();
let efeats = cpuid.get_extended_feature_info();
let efeats = efeats.as_ref();
Self {
sse: feats.is_some_and(|x| x.has_sse()),
sse2: feats.is_some_and(|x| x.has_sse2()),
sse3: feats.is_some_and(|x| x.has_sse3()),
ssse3: feats.is_some_and(|x| x.has_ssse3()),
sse4_1: feats.is_some_and(|x| x.has_sse41()),
sse4_2: feats.is_some_and(|x| x.has_sse42()),
avx: feats.is_some_and(|x| x.has_avx()),
f16c: feats.is_some_and(|x| x.has_f16c()),
fma: feats.is_some_and(|x| x.has_fma()),
avx2: efeats.is_some_and(|x| x.has_avx2()),
}
}
/// Whether SSE is supported.
pub fn sse(&self) -> bool {
self.sse
}
/// Whether SSE2 is supported.
pub fn sse2(&self) -> bool {
self.sse2
}
/// Whether SSE3 is supported.
pub fn sse3(&self) -> bool {
self.sse3
}
/// Whether SSSE3 is supported.
pub fn ssse3(&self) -> bool {
self.ssse3
}
/// Whether SSE4.1 is supported.
pub fn sse4_1(&self) -> bool {
self.sse4_1
}
/// Whether SSE4.2 is supported.
pub fn sse4_2(&self) -> bool {
self.sse4_2
}
/// Whether AVX is supported.
pub fn avx(&self) -> bool {
self.avx
}
/// Whether F16C is supported.
pub fn f16c(&self) -> bool {
self.f16c
}
/// Whether FMA is supported.
pub fn fma(&self) -> bool {
self.fma
}
/// Whether AVX2 is supported.
pub fn avx2(&self) -> bool {
self.avx2
}
}