Skip to content

Emulate AVX-512-only int64 ops (VPSRAQ/VPMULLQ) in the AVX2 path - #71

Merged
ajroetker merged 4 commits into
mainfrom
fix/avx2-int64-shift-mul-emulation
May 28, 2026
Merged

Emulate AVX-512-only int64 ops (VPSRAQ/VPMULLQ) in the AVX2 path#71
ajroetker merged 4 commits into
mainfrom
fix/avx2-int64-shift-mul-emulation

Conversation

@ajroetker

Copy link
Copy Markdown
Owner

Summary

Follow-up to #70. A library-wide disassembly scan (a binary linking every hwy/contrib/* package, disassembled with llvm-objdump) showed that several int64 functions still emitted AVX-512-only instructions in their AVX2 code path, which SIGILL on AVX2-only CPUs (the same EPYC 7763 / GitHub Actions runners from #68/#69):

Instruction archsimd op Functions
VPSRAQ Int64x4.ShiftAllRight (signed arithmetic shift) image.BaseForwardRCT/BaseInverseRCT, sort.BaseRadixPass/BaseRadixPassSigned, wavelet.BaseLiftPredict53/LiftUpdate53/Synthesize53Core/Synthesize53CoreCols
VPMULLQ Int64x4.Mul (64-bit multiply) image.BaseForwardRCT

These are the same root-cause class as #67/#68 but were outside that PR's scope.

Changes

  • hwygen codegen (transformer_ops.go): for the AVX2 target only, redirect 64-bit-int Mul and signed-int64 arithmetic ShiftAllRight to hwy emulation wrappers — extending the existing block that already does this for Max/Min (VPMAXSQ/VPMINSQ). Unsigned logical shifts (VPSRLQ) and left shifts (VPSLLQ) are AVX2-native and left untouched.
  • hwygen import detection (emitter.go): emit the hwy import when a function's only hwy reference is one of these redirected int64 ops. Previously the Max/Min redirect only got the import incidentally (when the function also used another hwy op); sort's radix pass uses only the shift and exposed the gap.
  • hwy emulation helpers (ops_avx2.go): Mul_AVX2_Int64x4, Mul_AVX2_Uint64x4, ShiftAllRight_AVX2_Int64x4, using the same scalar store/compute/load pattern as the existing Max_AVX2_Int64x4/Min_AVX2_Int64x4. Scalar is provably correct by inspection (Go's >> on a signed value is arithmetic; * is the low-64 product) — important since the AVX2 path can't be executed on the arm64 dev host.
  • Regenerated image/sort/wavelet AVX2 files (AVX-512/NEON/fallback unchanged). Added ops_avx2_test.go.

Behavior

  • AVX-512 CPUs: unchanged — still use native VPSRAQ/VPMULLQ (they only run where those exist; 23 such instructions remain in _avx512 functions).
  • AVX2-only CPUs: now use the scalar emulation instead of crashing. (Slower than native for these specific int64 ops, but correct; the prior behavior was SIGILL.)
  • NEON/fallback: unaffected.

Verification

  • Re-ran the all-contrib disassembly scan: zero EVEX (AVX-512) instructions remain in any _avx2 function.
  • Builds pass: linux/amd64, windows/amd64, arm64 (GOEXPERIMENT=simd).
  • Full test suite passes; hwy emulation-helper tests compile for amd64 (run on CI).

Test plan

  • CI passes on an AVX2-only (non-AVX-512) runner
  • GOEXPERIMENT=simd go test ./hwy/ exercises the new TestMulAVX2* / TestShiftAllRightAVX2Int64x4
  • image RCT / int64 radix sort / int64 wavelet lifting no longer SIGILL on AVX2-only CPUs

ajroetker added 4 commits May 28, 2026 11:15
A follow-up to the AVX2 SIGILL fixes: a library-wide disassembly scan found
that several int64 functions still emitted AVX-512-only instructions in their
AVX2 path, which SIGILL on AVX2-only CPUs:
- Int64x4.ShiftAllRight -> VPSRAQ (signed 64-bit arithmetic shift)
- Int64x4.Mul -> VPMULLQ (64-bit packed multiply)

Affected: image RCT (forward/inverse), int64 radix sort, and int64 wavelet
5/3 lifting.

hwygen now redirects these to scalar hwy emulation wrappers for the AVX2
target only (matching the existing Max/Min VPMAXSQ/VPMINSQ handling). The
AVX-512 target keeps the native instructions since it only runs where they
exist; NEON/fallback are unaffected. The emulation wrappers
(Mul_AVX2_Int64x4/Uint64x4, ShiftAllRight_AVX2_Int64x4) use the same
scalar store/compute/load pattern as the existing Max/Min wrappers.

Also fixes hwygen import detection so the hwy import is emitted when a
function's only hwy reference is one of these redirected int64 ops (the
previous Max/Min redirect only worked when the function used another hwy op).

Verified by disassembling a binary linking all contrib packages: no EVEX
(AVX-512) instructions remain in any _avx2 function. Added correctness tests
for the emulation helpers.
Disassembles a binary linking every hwy package and fails if any
go-highway function that isn't AVX-512-gated emits an EVEX-prefixed
(0x62) instruction. This makes the SIGILL bug class from #67-#69 and
this PR self-detecting instead of discovered reactively per-op.

Scoped to go-highway symbols excluding "avx512" names, so it also
catches EVEX leaking into generic/fallback helpers an AVX2-only CPU
could reach, while excluding our runtime-gated AVX-512 functions and
Go's own feature-gated runtime.asyncPreempt. Runs via go test and a
dedicated CI step that fails if the scan skips (missing llvm-objdump).
… dirs

- Move the cross-compile+disassemble integration test behind the
  "evexguard" build tag so it runs once in the dedicated CI step (and
  on-demand via `go test -tags evexguard`) instead of on every
  `go test ./...`. The fast parser unit test still runs by default.
- Treat a non-writable repo root (MkdirTemp failure) as a skip, matching
  the other prerequisite checks, so a read-only checkout can't spuriously
  fail the test.
- gitignore evexguard-scan-*/ so a leftover temp dir from an interrupted
  run can't be accidentally committed.
The step previously failed only on "--- SKIP", but a -run that matches
nothing (build-tag or test-name drift) prints "no tests to run" and
exits 0 -- letting the guard go green while scanning nothing. Gate on a
"--- PASS: TestNoEVEXInAVX2ReachableFunctions" line instead, so a skip,
a typo, or a tag mismatch all fail the build.
@ajroetker
ajroetker merged commit c3b5b59 into main May 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant