RISC-V Vector (RVV 1.0) SIMD support#100
Open
mikey wants to merge 2 commits into
Open
Conversation
Add pf_rvv_float.h and pf_rvv_double.h, providing the v4sf SIMD
contract (VADD/VMUL/VMADD, INTERLEAVE2/UNINTERLEAVE2, VTRANSPOSE4,
VSWAPHL, VREV_S/VREV_C, etc.) on top of GCC vector_size types so
that the compiler lowers them to RVV instructions
(vfadd.vv/vfmul.vv/vfmadd.vv/vrgather.vv/...).
The headers are gated on __riscv_vector together with the new
PFFFT_ENABLE_RVV define (added in a follow-up cmake change), so a
plain rv64gc build still falls back to the scalar implementation.
For float, SIMD_SZ=4 uses a 128-bit vector and works for any
VLEN >= 128. For double, SIMD_SZ=4 uses a 256-bit vector, matching
the existing AVX/SSE2 double layout; on hardware with VLEN >= 256
this is a single vfadd.vv per op, otherwise the compiler splits it.
Shuffles are routed through a small PF_RVV_SHUF4 helper that uses
__builtin_shufflevector when available (clang, GCC 12+) and falls
back to __builtin_shuffle on older GCC, so the headers compile
with both compilers. VALIGNED reports v4sf-sized alignment so the
existing test_pffft_{float,double} --test-simd self-test sees a
mix of aligned and unaligned addresses and passes cleanly.
README.md picks up RVV in the supported-SIMD list and a short
"Building on RISC-V" subsection describing the auto-enable.
Verified correctness with the existing test_pffft_float /
test_pffft_double suites (sizes 32..65536) and with --test-simd
on a SpaceMIT X100 (VLEN=256, gcc 15.2). Performance vs the
pure-scalar build (PFFFT_USE_SIMD=OFF, PFFFT_USE_SCALAR_VECT=OFF)
on real FFTs, PFFFT-U column, single core:
size float speedup double speedup
128 2.96x 2.05x
512 2.67x 2.32x
1024 2.78x 2.17x
4096 2.95x 2.20x
16384 2.62x 2.09x
65536 2.60x 2.04x
Assisted-by: Claude Code (Opus 4.7) <noreply@anthropic.com>
Mirror the aarch64/NEON auto-enable path for RISC-V: when the host
is riscv64 and the user has not pinned TARGET_C_ARCH/TARGET_CXX_ARCH,
pick an appropriate -march and define PFFFT_ENABLE_RVV so the new
RVV SIMD headers activate. When the user does set an explicit RISC-V
march that includes the V extension (matched via the regex
"rv[0-9]+[a-z_]*v($|_)" so plain rv64gc does not falsely match) we
leave the march alone and just define the macro.
The default march is decided by a single probe that compiles and
runs a tiny program against -march=rv64gcv:
- If the probe runs and prints a VLEN, the target supports V. The
default is rv64gcv, and if VLEN >= 256 the matching zvlNNNb is
appended so the compiler emits single-instruction 4-wide double
ops instead of splitting them into VLEN_min=128 pieces.
- If the probe fails to compile or fails to run, the target lacks
V. The default falls back to plain rv64gc and PFFFT_ENABLE_RVV
is not defined, so the build produces a working scalar binary
instead of one that SIGILLs on the first vector instruction.
The probe runs natively or under CMAKE_CROSSCOMPILING_EMULATOR
(e.g. qemu-riscv64-static); a bare cross-compile with no emulator
optimistically assumes V and falls back to rv64gcv, so users
targeting non-V cores should pass TARGET_C_ARCH=rv64gc explicitly.
Also add rv64gc/rv64gcv to the TARGET_C_ARCH cache value list so
the option shows sensible choices on RISC-V hosts in cmake-gui /
ccmake.
Add a build_riscv64_qemu CI job that cross-builds with the Ubuntu
gcc-riscv64-linux-gnu toolchain and runs ctest under
qemu-riscv64-static, mirroring the build_emscripten job for WASM.
This exercises the RVV headers, the cmake auto-detect path, and
the --test-simd shuffle/transpose validator on every PR without
requiring the maintainer to have RISC-V hardware.
Assisted-by: Claude Code (Opus 4.7) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a RISC-V Vector backend alongside the existing SSE/AVX/NEON/Altivec
paths, gated entirely on
__riscv_vector+ a newPFFFT_ENABLE_RVVbuild define so the change is a no-op for every other target.
Two commits:
Add RISC-V Vector (RVV 1.0) SIMD backend— newsrc/simd/pf_rvv_float.hand
pf_rvv_double.h, plus the matching include lines inpf_float.h/pf_double.h, theSIMD_*_HDRSlisting inCMakeLists.txt, and a short README mention.cmake: detect riscv64 and auto-enable RVV (+ CI job)— mirrors theaarch64/NEON auto-enable in
cmake/target_optimizations.cmake, plus anew
build_riscv64_qemuworkflow that exercises the headers underqemu-user-static.Design
v4sfis a GCCvector_sizetypedef (16 bytes for float, 32 fordouble), which the compiler lowers to RVV instructions (
vfadd.vv/
vfmul.vv/vfmadd.vv/vrgather.vv/ …).layout. On hardware with VLEN ≥ 256 this is a single instruction;
otherwise the compiler splits it into two ops. The build detects
the host VLEN via a tiny
try_runprobe and appends azvlNNNbtoken so the single-instruction form is used when available.
PF_RVV_SHUF4helper that picks__builtin_shufflevector(clang, GCC 12+) or__builtin_shuffle(older GCC), matching the same pattern used in
src/sse2neon.h.present, the build falls back to plain
rv64gc(noPFFFT_ENABLE_RVV, scalar path) instead of producing a binary thatSIGILLs.
Verification
Tested on a SpaceMIT X100 (8 cores, VLEN=256, Ubuntu 26.04):
ctest13/13 pass; bothtest_pffft_{float,double} --test-simdexit clean (the shuffle/transpose self-tests).ctest13/13, both--test-simdpass.RVV throughput trails gcc by ~10–25 % (clang's shuffle lowering
is less tight) but stays solidly above the scalar baseline.
-march=rv64gcv_zvl256band definesPFFFT_ENABLE_RVV; runtimepffft_simd_arch()reports"RVV",pffft_simd_size()reports4.TARGET_C_ARCH=rv64gcbuilds cleanly and runs the scalarpath — no spurious RVV define.
Performance vs the pure-scalar build
(
-DPFFFT_USE_SIMD=OFF -DPFFFT_USE_SCALAR_VECT=OFF), real FFT,PFFFT-Ucolumn, gcc 15.2, single core (taskset -c 0):Complex FFTs see roughly 1.3–1.4× as expected
(memory-bandwidth bound).
Deliberately not done
SIMD_SZ=4is hard-coded throughout pffft's pass functions, sothis backend stays at 4 lanes rather than scaling with VLEN.
Using LMUL > 1 or a VLEN-agnostic refactor would need changes
well outside the SIMD headers.
PFFFT_ENABLE_RVVis decided atcompile time, the same way
PFFFT_ENABLE_NEONis.What's tested vs not
hardware (correctness,
--test-simd, performance numbers above).TARGET_C_ARCH=rv64gc(no-V) fallback path on thesame hardware.
build_riscv64_qemuCI job has not yet run onGitHub Actions; the first push may need toolchain/QEMU
adjustments.
Happy to iterate on any of this — particularly the CMake glue and
the CI job — if there's a different shape you'd prefer.
Implementation done with Claude Code; see Assisted-by: trailers on the commits.