wide Rust crate

repository·main·Indexed 19 days ago

https://github.com/lokathor/wide

A Rust crate providing portable SIMD-capable data types as a near drop-in replacement for the unstable std::simd API. It supports x86, x86_64, wasm32, and aarch64 neon via the safe_arch crate, while using LLVM-optimized functions for other architectures. The library is #![no_std] by default and implements bytemuck::Pod for zero-overhead bitwise casts. It requires Rust version 1.89 and utilizes wrapping integer semantics and non-panicking operations to ensure performance.

Tokens
1.5K
Snippets
5
Records
10
Agent score
18%

What's inside wide

  1. Overview of the wide crate

    main

    The wide crate provides portable "wide" data types designed to utilize SIMD (Single Instruction, Multiple Data) instructions whenever possible. It serves as a near drop-in replacement for std::simd.

    Implementation details:

    • x86, x86_64, wasm32, and aarch64 neon: Uses explicit intrinsic usage via the safe_arch crate.
    • Other architectures: Uses carefully written functions to encourage LLVM to generate optimal SIMD instructions.

    Minimum Rust version required: 1.89.

  2. Understand the wide Rust Version Policy

    main

    The wide crate follows a specific versioning policy regarding the required Rust compiler version:

    1. The rust-version in Cargo.toml is always kept accurate to the required version.
    2. A bump in the required rust-version is treated as a major or minor version change, never a patch release. This allows users on older toolchains to continue receiving patch updates for their current major/minor version.
    3. Resolver Compatibility:
      • If using Resolver 3 (or later), dependency resolution will handle these version requirements automatically.
      • If using a Resolver earlier than 3, you are responsible for manually pinning the crate to a maximum compatible version if you are using an old Rust toolchain.
  3. Enable SIMD for x86 and x86_64 builds

    main

    Rust i686 and x86_64 targets only guarantee basic 128-bit vector operations (SSE2). To use additional SIMD extensions, you must enable them at build time using RUSTFLAGS.

    To target all SIMD extensions available on your current CPU, use -C target-cpu=native:

    RUSTFLAGS='-C target-cpu=native' cargo build --release

    Warning: Enabling specific extensions via target-cpu means the resulting binary may crash or exhibit undefined behavior if run on a CPU that does not support those instructions. Distributing binaries built this way is not recommended.

    Important Limitation: wide only supports detecting available SIMD extensions at build time. Runtime feature detection (e.g., using is_x86_feature_detected!) is not supported by wide.

  4. Enable SIMD for WASM builds

    main

    SIMD is an optional extension for WebAssembly (WASM). To enable SIMD in your build, you must set the RUSTFLAGS environment variable with the +simd128 target feature.

    Note: SIMD is supported by all modern browsers.

    RUSTFLAGS="-C target-feature=+simd128" cargo build --target wasm32-wasip1
  5. Integer wrapping and non-panicking semantics

    main

    SIMD vectors in wide follow specific safety and arithmetic rules designed for performance:

    1. Wrapping Semantics: Integer SIMD vectors treat arithmetic operators as wrapping (equivalent to core::num::Wrapping<T>). There are no overflow checks, even in debug builds, to ensure maximum performance.
    2. No Panicking: To support per-element branching via select, many operations that normally panic in the standard library do not panic in wide. For example, f32x4::clamp will not panic on invalid inputs; instead, it is expected that invalid elements are discarded later using a mask and select.
  6. How SIMD masks and the `select` method work

    main

    In wide, SIMD vector masks are represented by SIMD vectors where each element's value is either all zeros (false) or all ones (true).

    Functions that typically return a bool in scalar code (like f32::is_sign_positive) return a mask vector in SIMD code (like f32x4::is_sign_positive).

    You can perform per-element conditional logic using the select method. This acts like a vectorized if-else statement: if the mask bit is true, the value from the first argument is chosen; otherwise, the value from the second argument is chosen.

    # use wide::f32x4;
    
    let x = f32x4::new([1.0, -1.0, -1.0, 1.0]);
    
    // is_sign_positive() returns a mask vector
    let result = x.is_sign_positive().select(
        f32x4::splat(5.0), // value if true
        f32x4::splat(3.0), // value if false
    );
    
    assert_eq!(result, f32x4::new([5.0, 3.0, 3.0, 5.0]));
  7. Bitwise casting SIMD types with `bytemuck`

    main

    All SIMD types in wide implement the bytemuck::Pod trait. This allows you to perform zero-overhead bitwise casts between SIMD types of the same size using the bytemuck::cast() function. The bytemuck crate is re-exported by wide for convenience.

    // Example of using the re-exported bytemuck
    use wide::bytemuck;
    
    // Assuming types of the same size are used
    // let casted = bytemuck::cast::<f32x4, u32x4>(my_f32x4);
  8. Enable `std` for improved `sqrt` performance

    main
    By default, wide is a #![no_std] crate. You can enable the std feature to link to the standard library. This currently provides a performance benefit for sqrt operations when an explicit SIMD sqrt instruction is not available.
  9. Reference of supported SIMD types

    main

    The crate provides various SIMD vector types for different bit widths and data types. These are organized by their element type and vector length (e.g., f32x4 is a vector of four 32-bit floats).

    // Floating point types
    f32x16, f32x8, f32x4
    f64x8, f64x4, f64x2
    
    // Signed integer types
    i8x16, i8x32
    i16x16, i16x32, i16x8
    i32x4, i32x8, i32x16
    i64x2, i64x4, i64x8
    
    // Unsigned integer types
    u8x16, u8x32
    u16x8, u16x16, u16x32
    u32x4, u32x8, u32x16
    u64x2, u64x4, u64x8