The length sort in FFTIndexer::FilterFFTResults recomputed Coord::Length() inside
its comparator. Under LTO with -march=x86-64-v3 (CI and production builds) GCC
inlines Length() at several sites in std::__introsort_loop and contracts
x*x+y*y+z*z into FMAs differently at each (fma(x,x,y*y) for the element keys,
fma(y,y,x*x) for the pivot recomputed after a swap). The same element's key then
differs by one ulp between comparisons. Shortlist lengths come from quantised FFT
bins, so exact ties are common on noise frames; on such a tie the unguarded
partition scan passes its sentinel and runs off the index array.
Evidence:
- rc.172 jfjoch_broker disassembly: pivot key after swap at 0x925a83 is rounded
differently from the scan keys.
- The deployed rc.172 introsort, called directly on finite golden-spiral
directions x binned lengths, faults at binary +0x5259ca (the journal's crash
address) in up to ~0.5% of sorts. No NaN is involved; the earlier NaN guards
could not help.
- New test FFTIndexer_ManyNoiseFrames: unfixed rc.172 built with the CI flags
(-march=x86-64-v3 -flto=auto) segfaults in the same introsort from
FilterFFTResults on the GPU FFT path (noise frame 1632), 3/3 runs; passes with
this fix. A non-LTO build keeps Length() out of line and cannot crash, which
is why the suite never caught it.
Keys are now precomputed and sorted with stable_sort. The same pattern was
fixed in SpindleBlindFraction (broker-reachable, 62 lattice rows, |a|=|b| ties)
and LePageLattice::PlaneBasis (rugnux, v/-v exact ties); tie order in the
latter may change.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>