COOLJAPAN
← All posts

OxiFFT 0.4.2 Released — Wrong FFT Answers on Every Non-AVX2 x86_64 CPU, Found and Fixed

OxiFFT 0.4.2 fixes a wrong-answer bug (not a precision bug) in the x86_64 SSE3 SIMD butterfly kernel that every non-AVX2 x86_64 CPU takes — a wrong complex-multiply lane order and twiddle-recurrence drift together failed 93 of 1447 tests by 10-100x the tolerance. Found by running the suite on x86_64 for the first time. Plus a Miri-driven pointer-provenance fix, four new soundness hardenings against attacker-influenceable wisdom strings, and a fuzz-harness tolerance bug of its own. Pure Rust FFT, no FFTW.

release oxifft fft simd x86_64 pure-rust soundness miri correctness

A wrong-answer bug that agreed with the correct answer at exactly 45 degrees. That is precisely specific enough to hide from every test that didn’t happen to probe that angle.

Today we released OxiFFT 0.4.2 — a correctness release. Its headline fix: every non-AVX2 x86_64 CPU running OxiFFT’s SIMD path was computing wrong FFT results, not slightly-imprecise ones. It shipped that way because the project’s primary development host is aarch64, there is no CI, and this was the first time the suite ran on x86_64 at all.

No C. No Fortran. No FFTW. OxiFFT is a Pure Rust FFT library — this release doesn’t change that, it changes what the x86_64 SIMD tier actually computes.

Why OxiFFT 0.4.2 is a game changer

cargo nextest run -p oxifft --all-features --target x86_64-apple-darwin (via Rosetta 2, which exposes SSE3 and no AVX) failed 93 of 1447 tests — errors of magnitude 10-100 against a 1e-10 tolerance, cascading through chirp_z, conv, the DFT codelets and solvers, rdft::solvers::r2r, signal::{hilbert,resample}, sparse, and the size-coverage sweeps. Everything that bottoms out in the shared SIMD butterfly engine was affected. Two independent defects in the SSE3 kernel every non-AVX2 x86_64 CPU takes:

OxiFFT 0.4.2 ends both: the shuffle is removed (addsub’s operand was already in the correct lane order), and the kernel now reads the same precomputed twiddle table the AVX2 and NEON kernels already used. x86_64 is now 1455/1455 passing, aarch64 stays green at 1765/1765, and a new absolute-accuracy test (test_simd_butterfly_matches_naive_dft, checked against a directly evaluated DFT rather than the equally-drifting scalar recurrence) pins it down.

Stated plainly, because it matters: Rosetta 2 only exercises the SSE2/SSE3 tier. Every AVX/AVX2/AVX-512 code path has still never executed on real hardware — no AVX2-capable machine is available to this project. One function, dit_butterflies_avx2 (the tier a real post-2013 desktop or server takes), was hand-audited lane-by-lane as part of this fix and checks out. Don’t read “x86_64 fixed” as “the AVX tier is verified.”

Technical Deep Dive: everything else in 0.4.2

  1. Miri-driven pointer-provenance fix. The first Miri run over the crate’s unsafe surface flagged api::parallel::RawPtr’s ptr as usize / usize as *mut T round trip — it strips provenance, so derived pointer arithmetic was no longer provably in-bounds under Stacked/Tree Borrows. RawPtr now carries *mut u8 directly; Miri is clean over api::parallel, api::memory, api::plan, kernel::complex_mul, dft::problem, and dft::plan (98 tests).
  2. Wisdom-string hardening. Wisdom is attacker-influenceable input — importable from a string, file, or system path — and an entry is only (size, name, cost). A planted (1024 "nop" 1.0) used to make execute_inplace silently return the input unchanged as an “FFT result”; a planted (6 "ct-dit" 1.0) drove the radix-2 engine past its debug-only size guard. Nine more solver-name arms are now re-validated against the transform size the same way "composite"/"rader" already were.
  3. AVX-512 entry-point bounds checks. Six pub dispatch_hand_avx512_size{16,32,64}_{f32,f64} functions handed any-length safe slices to raw-pointer codelets that unconditionally read/wrote exactly N elements — an out-of-bounds access reachable with no unsafe in the caller. Each now assert_eq!s the slice length first.
  4. ThreadPool::parallel_for’s “exactly once per index” invariant is no longer just trusted. A new one-shot IndexClaims table makes every raw-pointer partitioning site (row/column/fiber splits) degrade to incomplete results instead of out-of-bounds writes or aliased &mut if a pool implementation ever violates the contract.

Getting Started

[dependencies]
oxifft = { version = "0.4.3", features = ["sparse", "streaming", "signal"] }
use oxifft::{fft, ifft, Complex};

let data = vec![Complex::new(1.0, 0.0); 256];
let spectrum = fft(&data)?;
let recovered = ifft(&spectrum)?;

What’s New in 0.4.2

Tips

This is the foundation

Correct SIMD arithmetic and Miri-clean unsafe code matter most for whatever builds numerically on top. SciRS2, NumRS2, ToRSh, TrustFormers, SkLearS, VoiRS, OxiONNX, OxiMedia, and Kizzasi all pin oxifft for FFT/DFT/DCT transforms.

Repository: https://github.com/cool-japan/oxifft

Star the repo if “we ran it on the other architecture for the first time and found a wrong-answer bug” is the kind of honesty you want from a library you depend on.

The era of SIMD kernels nobody ran on the second architecture is over. Pure Rust FFT that’s fast, safe, and sovereign — is here.

KitaSan at COOLJAPAN OÜ August 6, 2026

↑ Back to all posts