Conversation
…egister exhaustion
The v3.3.0 branch-free sign-pack fill loop keeps a Vec{64,UInt32} NCO
accumulator live. On the AVX2 backend that is 8 of the 16 YMM registers,
and together with the sign-pack temporaries it overflows the register
file — LLVM 15 (Julia 1.10) aborts codegen with "couldn't allocate output
register for constraint 'x'". AVX-512 escapes it (4 ZMM per value, 32
registers), which is why it only surfaces on AVX2-only CI runners.
Give AVX2 a dedicated `_signs_fill_flips!` / `_sm_fill_flips!` that keeps a
32-lane (half-word) accumulator resident (4 YMM) plus a broadcast half-word
offset, evaluating each word in two 32-lane halves — the width the AVX2
`_flip2`/`_sm_flip3` already slice to. Mirrors the existing NEON
16-lane-quarter fill. Bit-identical output, verified against a scalar
reference across the issue's frequency and full/partial-tail edge cases.
Closes #25
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #26 +/- ##
==========================================
- Coverage 81.06% 76.08% -4.98%
==========================================
Files 8 8
Lines 565 598 +33
==========================================
- Hits 458 455 -3
- Misses 107 143 +36 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Benchmark Results (minimum time) — ubuntu-latestReporting the minimum over all samples (robust to shared-runner contention), not the median. Ratio = 2ac3059… / fad44e0…: >1 means the PR is faster. ✅ ≥ 5 % faster,
Time benchmarks
Memory benchmarks
|
Benchmark Results (minimum time) — macos-14Reporting the minimum over all samples (robust to shared-runner contention), not the median. Ratio = 2ac3059… / fad44e0…: >1 means the PR is faster. ✅ ≥ 5 % faster,
Time benchmarks
Memory benchmarks
|
Summary
Fixes #25 —
generate_carrier_signs!(andgenerate_carrier_signs_mags!) crash LLVM codegen on Julia 1.10 (LLVM 15) when SinCosLUT selects its AVX2 backend:Root cause
The v3.3.0 branch-free sign-pack fill loop (
_signs_fill_flips!/_sm_fill_flips!) keeps aVec{64,UInt32}NCO accumulator live. On AVX2 that single value is 8 of the 16 YMM registers; with the sign-pack temporaries live at the same time, LLVM 15 cannot satisfy the'x'(SSE/AVX) register constraints and aborts codegen. AVX-512 escapes it (4 ZMM per value, 32 registers), which is why it only surfaces on AVX2-only CI runners and looks flaky across the fleet.Fix
Give AVX2 a dedicated
_signs_fill_flips!/_sm_fill_flips!that keeps a 32-lane (half-word) accumulator resident (4 YMM) plus a broadcast half-word offset, evaluating each word in two 32-lane halves — exactly the width the AVX2_flip2/_sm_flip3already slice down to. This mirrors the existing NEON 16-lane-quarter fill, and keeps the working set well within 16 YMM. The shared_flip2(::AVX2, ::Vec{64})/_sm_flip3(::AVX2, ::Vec{64})entry points (still used by the low-frequency run-fill straddle path) now delegate to the new half-width form, so there is a single source of truth.No behavioural change: output is bit-identical. AVX-512, NEON, and Portable paths are untouched.
Verification
AVX2(), which runs on this AVX-512 host since AVX2 ⊂ AVX-512) and confirmed bit-identical to a scalar reference across the issue'sfrequency = 1e6, sampling_frequency = 5e6case plus full-word, partial-tail, andfwboundary edge cases.Julia 1.10 / ubuntu-latestCI job (AVX2 + LLVM 15); the crash cannot be reproduced on newer LLVM or on AVX-512 hosts (per the issue, LLVM 16+ handles it).🤖 Generated with Claude Code