Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ You can find its changes [documented below](#060-2026-07-10).

- The `new_unchecked()` function on SIMD level tokens such as `Avx2` has been renamed to `assume_supported()` and is now safe to call from contexts that already contain the appropriate `#[target_feature]` annotations. Functions without such annotations can still call `assume_supported()` with an `unsafe` block. ([#293][] by [@Shnatsel][])
- On x86_64 targets with static SSE2 support, `Level::baseline()` now returns `Sse2` instead of `Fallback`. ([#270][] by [@Shnatsel][])
- The scalar `Fallback` backend and `Level::Fallback` variant are no longer compiled when the target has a better ambient SIMD baseline (e.g. SSE2 on x86, NEON on Aarch64). The `force_support_fallback` feature continues to make them available for testing. `disable_dispatch_sse2` no longer disables SSE2 if it is the baseline level.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have the pr number reference (by the way, I've been working on tooling to make our changelog management better - see https://github.com/DJMcNab/release_eng/blob/main/gazeto/README.md - which I'd appreciate if you had any feedback on).

@Shnatsel Shnatsel Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel my current workflow of having an LLM go through the git history and write a draft, then editing the PR summaries manually works well. See #314 for what the result looks like.

And frankly I don't want to be beholden to even more inflexible tools than this repository already is.

- The `fxsr` CPU feature is now required for all x86 SIMD levels. It is present in hardware on all SIMD-capable CPUs, but it is possible to disable it in some emulators combined with a custom Rust target specification. ([#270][] by [@Shnatsel][])
- All native-width non-mask vector types now share `u8s` as their byte representation, enabling `Bytes::bitcast` between arbitrary lane types in code generic over `Simd`. The byte representation of any `SimdBase` type is now also guaranteed to be an idempotent, same-token `u8` SIMD vector, so it can be manipulated directly in generic code.
- `SimdBase::Mask` now guarantees support for selecting vectors of its associated `SimdBase` type, enabling mask selection in generic code without additional bounds.
Expand Down
2 changes: 2 additions & 0 deletions fearless_simd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ As a last resort, you can turn off multiversioning for specific SIMD instruction
These configuration flags only control automatic multiversioning. Disabling one does not remove its token type, its
[`Simd`] implementation, or explicit [`kernel`] support; for example, an `Avx2` token can still be used to call an
AVX2 kernel when the CPU supports it.
`disable_dispatch_sse2` has no effect when SSE2 is part of the ambient target baseline, because
that baseline remains the terminal dispatch backend.

Note that later extensions can be beneficial even if you are only using 128-bit vectors:
AVX2 and AVX-512 provide more efficient instructions for some operations,
Expand Down
58 changes: 31 additions & 27 deletions fearless_simd/src/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,50 @@

#![expect(
missing_docs,
trivial_numeric_casts,
clippy::unnecessary_cast,
clippy::cast_possible_truncation,
clippy::unseparated_literal_suffix,
clippy::use_self,
clippy::wrong_self_convention,
reason = "Simplifies the generator and has no effect on the machine code"
)]
#![cfg_attr(
target_arch = "x86_64",
expect(
clippy::new_without_default,
reason = "TODO: https://github.com/linebender/fearless_simd/issues/40"
)
)]
#![cfg_attr(
target_arch = "wasm32",
expect(
clippy::new_without_default,
reason = "TODO: https://github.com/linebender/fearless_simd/issues/40"
)
)]
#![cfg_attr(
all(
feature = "std",
all(not(target_arch = "x86_64"), not(target_arch = "wasm32"))
),
expect(
clippy::new_without_default,
reason = "TODO: https://github.com/linebender/fearless_simd/issues/40"
)
#![allow(
trivial_numeric_casts,
clippy::unnecessary_cast,
clippy::new_without_default,
reason = "Simplifies the generator and has no effect on the machine code, only tripped by some backends"
)]

//! A module containing generated files
//!
//! All files in this subdirectory are autogenerated by the `fearless_simd_gen` crate.

// Fallback is unnecessary exactly when one of our SIMD backends is guaranteed as the baseline.
// Keep this predicate in sync with `Level::Fallback` and `dispatch!`.
macro_rules! with_fallback {
($($item:item)*) => {$(
#[cfg(any(
feature = "force_support_fallback",
not(any(
all(target_arch = "aarch64", target_feature = "neon"),
all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse2",
target_feature = "fxsr"
),
all(target_arch = "wasm32", target_feature = "simd128")
))
))]
$item
)*};
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod avx2;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod avx512;
mod fallback;
with_fallback! {
mod fallback;
}
#[cfg(target_arch = "aarch64")]
mod neon;
mod ops;
Expand All @@ -61,7 +63,9 @@ mod wasm;
pub use avx2::*;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub use avx512::*;
pub use fallback::*;
with_fallback! {
pub use fallback::*;
}
#[cfg(target_arch = "aarch64")]
pub use neon::*;
pub use simd_trait::*;
Expand Down
69 changes: 65 additions & 4 deletions fearless_simd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@
//! These configuration flags only control automatic multiversioning. Disabling one does not remove its token type, its
//! [`Simd`] implementation, or explicit [`kernel`] support; for example, an `Avx2` token can still be used to call an
//! AVX2 kernel when the CPU supports it.
//! `disable_dispatch_sse2` has no effect when SSE2 is part of the ambient target baseline, because
//! that baseline remains the terminal dispatch backend.
//!
//! Note that later extensions can be beneficial even if you are only using 128-bit vectors:
//! AVX2 and AVX-512 provide more efficient instructions for some operations,
Expand Down Expand Up @@ -352,7 +354,7 @@ fn detect_x86_level() -> Level {
// Safety: All features required by Sse2 were detected above.
unsafe { Level::Sse2(Sse2::assume_supported()) }
} else {
Level::Fallback(Fallback::new())
Level::baseline()
}
}

Expand All @@ -365,7 +367,25 @@ fn detect_x86_level() -> Level {
pub enum Level {
/// Scalar fallback level, i.e. no supported SIMD features are to be used.
///
/// This variant is **absent** on targets that supports a higher baseline
/// (`aarch64-*`, `i686-*`, `x86_64-*`, WASM with SIMD) unless the `force_support_fallback`
/// Cargo feature is enabled. Instead of matching on this variant,
/// call [`is_fallback`](Level::is_fallback) which is always available.
///
/// This can be created with [`Level::fallback`].
Comment thread
Shnatsel marked this conversation as resolved.
// Keep this predicate in sync with the fallback module and `dispatch!`.
#[cfg(any(
feature = "force_support_fallback",
not(any(
all(target_arch = "aarch64", target_feature = "neon"),
all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse2",
target_feature = "fxsr"
),
all(target_arch = "wasm32", target_feature = "simd128")
))
))]
Fallback(Fallback),
/// The Neon instruction set on 64 bit ARM.
#[cfg(target_arch = "aarch64")]
Expand Down Expand Up @@ -450,8 +470,29 @@ impl Level {
/// Check whether this is the `Fallback` level; that is, whether no better feature level could
/// be statically or dynamically detected. This is useful if there's a scalarized version of
/// your algorithm that runs faster if SIMD isn't supported.
///
/// This method is always available, even when the fallback backend is not compiled. In that
/// case, it always returns `false`.
pub fn is_fallback(self) -> bool {
matches!(self, Self::Fallback(_))
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
return self.as_sse2().is_none();

#[cfg(target_arch = "aarch64")]
return self.as_neon().is_none();

#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
return self.as_wasm_simd128().is_none();

#[cfg(any(
all(target_arch = "wasm32", not(target_feature = "simd128")),
not(any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "wasm32"
))
))]
return true;
}

/// If this is a proof that Neon (or better) is available, access that instruction set.
Expand Down Expand Up @@ -799,7 +840,12 @@ impl Level {
return Self::Sse4_2(sse4_2);
}

#[cfg(not(disable_dispatch_sse2))]
// The ambient SSE2 baseline is the terminal backend and cannot be disabled. Falling
// below it would require compiling the otherwise-unneeded fallback implementation.
#[cfg(any(
not(disable_dispatch_sse2),
all(target_feature = "sse2", target_feature = "fxsr")
))]
if let Some(sse2) = self.as_sse2().or_else(|| baseline.as_sse2()) {
return Self::Sse2(sse2);
}
Expand All @@ -824,7 +870,7 @@ impl Level {
}
}

Self::Fallback(Fallback::new())
Self::baseline()
}

/// Create a scalar fallback level, which uses no SIMD instructions.
Expand Down Expand Up @@ -852,6 +898,21 @@ mod tests {
assert_is_send_sync::<Level>();
}

#[test]
fn baseline_reports_whether_fallback_is_required() {
let has_simd_baseline = cfg!(any(
all(target_arch = "aarch64", target_feature = "neon"),
all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse2",
target_feature = "fxsr"
),
all(target_arch = "wasm32", target_feature = "simd128")
));

assert_eq!(Level::baseline().is_fallback(), !has_simd_baseline);
}

#[cfg(all(
any(feature = "std", target_arch = "wasm32"),
not(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))
Expand Down
Loading
Loading