Fearless SIMD

repository·main·Indexed 43 days ago

https://github.com/linebender/fearless_simd

A Rust library providing safe, portable SIMD abstractions. It features a `dispatch!` macro for runtime selection of optimal CPU instruction sets, a `kernel!` macro for safe access to raw intrinsics (such as NEON or AVX), and support for WebAssembly simd128. Version 1.0.0-rc.1 requires Rust 1.89 or later.

Tokens
11.6K
Snippets
24
Records
57
Agent score
60%

What's inside Fearless SIMD

  1. Overview of Fearless SIMD

    main

    Fearless SIMD is a Rust crate designed to provide safe and easy access to SIMD (Single Instruction, Multiple Data) capabilities. It allows developers to use both low-level intrinsics and high-level safe abstractions with minimal unsafe code in the crate itself.

    For detailed usage instructions, API references, and specific examples, refer to the package-level documentation in the fearless_simd directory.

  2. Best practices for Inlining in Fearless SIMD

    main

    Fearless SIMD relies heavily on Rust's inlining to enable specific target features for SIMD functions. Follow these rules:

    • Always mark SIMD functions with #[inline(always)].
    • Use the dispatch! macro when calling SIMD code from non-SIMD code.
    • Use Simd::vectorize() when calling SIMD code from other SIMD code if you do not want to force inlining.
  3. Understand the security backport policy

    main

    Starting with v1.0, Fearless SIMD provides security backports for the latest version of the library for each Minimum Supported Rust Version (MSRV) for at least 3 years after that MSRV was released.

    This policy ensures that security updates can be applied via a simple cargo update, provided your Rust compiler version is not older than 3 years.

    Backporting Logic Example:

    • If version 1.1.x and 1.2.x both share the same MSRV, only 1.2.x (the latest) will receive security backports. Version 1.1.x will not, because cargo update would naturally pull the fixed version from the 1.2.x series.
    • When a major version (e.g., v2.0.0) is released, the previous major version series (v1.x.x) will continue to receive security backports according to this policy.
  4. Enable automatic vectorization with `dispatch!`

    main

    To achieve automatic vectorization, write your logic in a function generic over the Simd trait and mark it with #[inline(always)]. You can then use the dispatch! macro to generate multiple implementations for different SIMD levels and select the optimal one at runtime based on the CPU's capabilities.

    use fearless_simd::{dispatch, Level, Simd};
    
    #[inline(always)]
    fn double_u32s<S: Simd>(_: S, values: &mut [u32]) {
        for value in values {
            *value = *value * 2;
        }
    }
    
    let mut values = [1, 2, 3, 4, 5];
    let level = Level::new();
    dispatch!(level, simd => double_u32s(simd, &mut values));
    assert_eq!(values, [2, 4, 6, 8, 10]);
  5. Access raw intrinsics safely with `kernel!`

    main

    If you need to use hardware-specific intrinsics (like NEON or AVX), use the kernel! macro. This macro creates a function where you can safely call raw intrinsics by passing a hardware-specific token (e.g., Neon). You can then check for support at runtime using level.as_neon() (or the relevant architecture method) and dispatch to the kernel.

    use fearless_simd::{prelude::*, Level, u32x4};
    
    fearless_simd::kernel!(
        fn double_u32s_neon(neon: Neon, values: &mut [u32]) {
            use core::arch::aarch64::*;
    
            let mut chunks = values.chunks_exact_mut(4);
            for chunk in &mut chunks {
                let v: uint32x4_t = u32x4::from_slice(neon, chunk).into(); // safe load
                let doubled = vmulq_u32(v, vdupq_n_u32(2)); // safe access to a NEON intrinsic
                let doubled: u32x4<_> = doubled.simd_into(neon);
                doubled.store_slice(chunk);
            }
            for value in chunks.into_remainder() {
                *value = *value * 2;
            }
        }
    );
    
    #[cfg(target_arch = "aarch64")]
    {
        let level = Level::new();
        if let Some(neon) = level.as_neon() {
            let mut values = [1, 2, 3, 4, 5];
            double_u32s_neon(neon, &mut values);
            assert_eq!(values, [2, 4, 6, 8, 10]);
        }
    }
  6. Manage x86 multiversioning and binary size

    main

    On x86, fearless_simd compiles versions for various instruction sets (SSE2, SSE4.2, AVX2, AVX-512) and uses dispatch! to select the best one. This increases binary size.

    Mitigation strategies:

    • Set codegen-units = 1 or lto = true in Cargo.toml.
    • Disable specific instruction sets globally via RUSTFLAGS using these flags:
      • --cfg disable_dispatch_sse2
      • --cfg disable_dispatch_sse4_2
      • --cfg disable_dispatch_avx2
      • --cfg disable_dispatch_avx512

    Note: Disabling a dispatch does not remove the token type or explicit kernel! support; it only controls automatic multiversioning.

  7. Report a vulnerability in Fearless SIMD

    main

    Fearless SIMD follows a full disclosure policy. If you discover a security vulnerability, please report it publicly on the GitHub issue tracker.

    Note on AI-generated reports: Entirely AI-generated reports are not permitted. While you are welcome to use LLMs for auditing code, a human must reproduce and understand the issue before submitting the report.

  8. Run WebAssembly +simd128 tests

    main

    To test fearless_simd functionality within a WebAssembly environment using simd128 and relaxed-simd features, you must use a Wasm runtime and pass specific target features via cargo configuration flags.

    Prerequisites

    Install a WebAssembly runtime. You can use either wasmtime or wasmi:

    Using wasmtime:

    cargo install --locked wasmtime-cli

    Using wasmi (with simd support):

    cargo install --locked --features simd wasmi_cli

    Execution

    Run the tests using the wasm32-wasip1 target, ensuring the SIMD features are enabled in both the compilation and the runner configuration.

    cargo test --target wasm32-wasip1 \
        --config 'target.wasm32-wasip1.rustflags = "-Ctarget-feature=+simd128,+relaxed-simd"' \
        --config 'target.wasm32-wasip1.rustdocflags = "-Ctarget-feature=+simd128,+relaxed-simd"' \
        --config 'target.wasm32-wasip1.runner = "wasmtime"' # or "wasmi_cli" if you installed that
  9. Disable AVX2 for a single function

    main
    You can disable a specific SIMD instruction set (like AVX2) for a single function. This is useful when a specific instruction set might cause issues or when you want to provide a fallback path for a specific computation. For global controls or other multiversioning strategies, refer to the crate's main README.
  10. Configure WebAssembly SIMD support

    main

    WebAssembly SIMD does not support feature detection. You must compile two versions of your WASM bundle (one with SIMD and one without) and select the correct one at runtime using a library like wasm-feature-detect.

    To enable SIMD128, use the RUSTFLAGS environment variable:

    RUSTFLAGS="-Ctarget-feature=+simd128"

    Alternatively, you can configure this in your Cargo.toml:

    [target.'cfg(target_arch = "wasm32")']
    rustflags = ["-Ctarget-feature=+simd128"]
    rustdocflags = ["-Ctarget-feature=+simd128"]

    For Relaxed SIMD (currently Chrome only), build with:

    RUSTFLAGS="-Ctarget-feature=+simd128,+relaxed-simd"