lance-format / lance-format/lance

bug: safe SIMD From impls execute AVX loads with no runtime gate on x86_64

Open
#8,872 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug performance
Dominant language
Rust
Stars
7.1k
Forks
852
Avg merge
3d 18h
Merged PRs (30d)
272

Description

impl From<&[f32]> for f32x8 is a safe function that reaches an AVX instruction with no runtime check and no declared baseline:

impl From<&[f32]> for f32x8 {
    fn from(value: &[f32]) -> Self {
        assert!(value.len() >= 8, ...);
        unsafe { Self::load_unaligned(value.as_ptr()) }
    }
}

SIMD::load_unaligned for f32x8 is rust/lance-linalg/src/simd/f32.rs:217, which is _mm256_loadu_ps(ptr). In core::arch that intrinsic carries #[target_feature(enable = "avx")], so calling it on a host without AVX is undefined behaviour, not merely a SIGILL. Nothing on the path checks: load_unaligned has no #[target_feature] of its own, the impl does no is_x86_feature_detected!, and neither lib.rs nor Cargo.toml declares an AVX baseline for the crate.

The same shape covers f32x16 (f32.rs:615, two _mm256_loadu_ps), and the sibling types in simd/f64.rs, simd/i32.rs and simd/u8.rs load through the corresponding _mm256_loadu_* / _mm_loadu_* intrinsics from safe From impls.

What makes this reachable rather than theoretical is that the crate already documents a fallback that lands here. From the doc on f32x8::gather (f32.rs:50):

On other architectures (and on x86_64 hosts without AVX2) the function falls back to a per-index scalar load followed by Self::from(&out), which goes through load_unaligned (NEON / LASX / _mm256_loadu_ps depending on platform).

So the path taken specifically because AVX2 was not detected ends in an AVX instruction.

In-repo builds are insulated by accident: .cargo/config.toml sets target-cpu=haswell with +avx2,+fma,+f16c for x86_64-unknown-linux-gnu, so every binary built in this repo has AVX. That file does not travel with the published crate, so a downstream crate that depends on lance-linalg and builds for a generic x86_64 target gets no such baseline.

I have not sent a patch because each of the three fixes trades away something a maintainer should weigh:

  1. Runtime-gate the loads with is_x86_feature_detected!("avx") and a scalar fallback. Correct everywhere, but it puts a branch in the innermost load of every kernel.
  2. Make the conversions unsafe (drop the From impls in favour of an unsafe fn). No runtime cost, but it is a breaking change to a public API, and From is what the call sites use today.
  3. Declare the baseline and fail the build, e.g. #[cfg(all(target_arch = "x86_64", not(target_feature = "avx")))] compile_error!(...). Zero runtime cost and turns the UB into a compile error, but it makes the crate unbuildable for a pre-Sandy-Bridge or feature-masked x86_64 target that works today.

Correction to my own last paragraph, since it inverts the conclusion: x86-64-v2 does not include AVX (that is v3), so qemu-pre-haswell is close to a ready-made reproduction rather than blind to this. That job builds with -C target-cpu=x86-64-v2 and runs under an emulated Nehalem, which has no AVX at all. It stays green today because at that tier SIMD_SUPPORT is None, the scalar arms run, and nothing constructs an f32x8; the in-crate uses of the type in distance/cosine.rs all sit inside #[target_feature(enable = "avx,fma")] unsafe fn, which is the correct pattern. A test in that job that calls f32x8::from(&slice[..8]) directly would fault, which is the cheapest way to demonstrate the gap and to keep it closed.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the safe From implementations and load_unaligned in rust/lance-linalg/src/simd/f32.rs, then compare the sibling implementations in simd/f64.rs, simd/i32.rs, and simd/u8.rs. Read f32x8::gather and the qemu-pre-haswell job, and reproduce the direct conversion on a no-AVX target. Done means the unsafe path is no longer reachable without the required CPU feature, with coverage for the affected conversions.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.