diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dbf830..eca85e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,21 @@ option(KISSFFT_STATIC "Build kissfft as static (ON) or shared library (OFF)" OFF option(KISSFFT_TEST "Build kissfft tests" ON) option(KISSFFT_TOOLS "Build kissfft command-line tools" ON) option(KISSFFT_USE_ALLOCA "Use alloca instead of malloc" OFF) +option(KISSFFT_USE_SIMDE "Use SIMDE for portable SIMD" OFF) + +# Detect x86 architecture +string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _kf_proc) +if(_kf_proc MATCHES "^(x86_64|amd64|i[3-6]86)$") + set(KISSFFT_X86 TRUE) +else() + set(KISSFFT_X86 FALSE) +endif() + +# Auto-enable SIMDE on non-x86 when using simd datatype +if(KISSFFT_DATATYPE STREQUAL "simd" AND NOT KISSFFT_X86) + set(KISSFFT_USE_SIMDE ON) + message(STATUS "Non-x86 detected, auto-enabling SIMDE for SIMD support") +endif() # # Validate datatype @@ -165,10 +180,14 @@ else() if(KISSFFT_DATATYPE MATCHES "^simd$") list(APPEND KISSFFT_COMPILE_DEFINITIONS USE_SIMD) - if (NOT MSVC) - target_compile_options(kissfft PRIVATE -msse) - else() - target_compile_options(kissfft PRIVATE "/arch:SSE") + if(KISSFFT_USE_SIMDE) + list(APPEND KISSFFT_COMPILE_DEFINITIONS USE_SIMD_SIMDE) + elseif(KISSFFT_X86) + if(MSVC) + target_compile_options(kissfft PRIVATE "/arch:SSE") + else() + target_compile_options(kissfft PRIVATE -msse) + endif() endif() endif() endif() @@ -325,12 +344,11 @@ if (KISSFFT_PKGCONFIG) set(PKGCONFIG_KISSFFT_VERSION "${kissfft_VERSION}") join_paths(PKGCONFIG_KISSFFT_LIBDIR "\${prefix}" "${CMAKE_INSTALL_LIBDIR}") join_paths(PKGCONFIG_KISSFFT_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") - if(KISSFFT_DATATYPE MATCHES "^simd$") - list(APPEND KISSFFT_COMPILE_DEFINITIONS USE_SIMD) - if (NOT MSVC) - set(PKG_KISSFFT_DEFS "${PKG_KISSFFT_DEFS} -msse") - else() + if(KISSFFT_DATATYPE MATCHES "^simd$" AND NOT KISSFFT_USE_SIMDE AND KISSFFT_X86) + if(MSVC) set(PKG_KISSFFT_DEFS "${PKG_KISSFFT_DEFS} /ARCH:SSE") + else() + set(PKG_KISSFFT_DEFS "${PKG_KISSFFT_DEFS} -msse") endif() endif() if (NOT KISSFFT_OPENMP) diff --git a/Makefile b/Makefile index 7ab2d53..1c5e4ed 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,7 @@ export KISSFFT_OPENMP ?= 0 export KISSFFT_STATIC ?= 0 export KISSFFT_TOOLS ?= 1 export KISSFFT_USE_ALLOCA ?= 0 +export KISSFFT_USE_SIMDE ?= 0 # # Installation directories @@ -114,6 +115,17 @@ endif export KISSFFTLIB_SHORTNAME +# Detect x86 architecture for native SSE +_KISSFFT_ARCH := $(shell uname -m) +_KISSFFT_X86 := $(filter x86_64 i386 i486 i586 i686 amd64,$(_KISSFFT_ARCH)) + +# Auto-enable SIMDE on non-x86 when using simd datatype +ifeq "$(KISSFFT_DATATYPE)" "simd" + ifeq "$(_KISSFFT_X86)" "" + override KISSFFT_USE_SIMDE = 1 + endif +endif + # # Compile-time definitions by datatype # @@ -127,7 +139,12 @@ ifeq "$(KISSFFT_DATATYPE)" "int32_t" else ifeq "$(KISSFFT_DATATYPE)" "int16_t" TYPEFLAGS += -DFIXED_POINT=16 else ifeq "$(KISSFFT_DATATYPE)" "simd" - TYPEFLAGS += -DUSE_SIMD=1 -msse + TYPEFLAGS += -DUSE_SIMD=1 + ifeq ($(KISSFFT_USE_SIMDE), 1) + TYPEFLAGS += -DUSE_SIMD_SIMDE + else + TYPEFLAGS += -msse + endif else ifeq "$(KISSFFT_DATATYPE)" "float" TYPEFLAGS += -Dkiss_fft_scalar=$(KISSFFT_DATATYPE) else ifeq "$(KISSFFT_DATATYPE)" "double" diff --git a/README.simd b/README.simd index b0fdac5..782fae0 100644 --- a/README.simd +++ b/README.simd @@ -9,6 +9,31 @@ This API is not easy to use, is not well documented, and breaks the KISS princip Still reading? Okay, you may get rewarded for your patience with a considerable speedup (2-3x) on intel x86 machines with SSE if you are willing to jump through some hoops. +Portable SIMD Support with SIMDE +================================ + +kissfft now supports SIMDE (SIMD Everywhere) for portable SIMD operations on non-x86 +platforms such as ARM, RISC-V, LoongArch, WebAssembly, and others. + +To enable SIMDE support: + +With CMake: + cmake -DKISSFFT_DATATYPE=simd -DKISSFFT_USE_SIMDE=ON .. + +With Makefile: + make KISSFFT_DATATYPE=simd KISSFFT_USE_SIMDE=1 + +Prerequisites: +- SIMDE headers must be available in your include path +- Install via package manager or include SIMDE as a submodule in your project + +When SIMDE is enabled, the library will use SIMDE's portable SSE implementation, +which automatically maps to native SIMD instructions on the target platform or +falls back to scalar operations if no SIMD is available. + +Native x86 SSE Support +====================== + The basic idea is to use the packed 4 float __m128 data type as a scalar element. This means that the format is pretty convoluted. It performs 4 FFTs per fft call on signals A,B,C,D. @@ -19,9 +44,12 @@ where "rA0" is the real part of the zeroth sample for signal A Real-only data is laid out: rA0,rB0,rC0,rD0, rA1,rB1,rC1,rD1, ... -Compile with gcc flags something like +Compile with gcc flags something like (for native x86 SSE): -O3 -mpreferred-stack-boundary=4 -DUSE_SIMD=1 -msse +Or for portable SIMD with SIMDE: +-O3 -mpreferred-stack-boundary=4 -DUSE_SIMD=1 -DUSE_SIMD_SIMDE + Be aware of SIMD alignment. This is the most likely cause of segfaults. The code within kissfft uses scratch variables on the stack. With SIMD, these must have addresses on 16 byte boundaries. diff --git a/_kiss_fft_guts.h b/_kiss_fft_guts.h index 4bd8d1c..56381a7 100644 --- a/_kiss_fft_guts.h +++ b/_kiss_fft_guts.h @@ -129,9 +129,15 @@ struct kiss_fft_state{ # define KISS_FFT_SIN(phase) floor(.5+SAMP_MAX * sin (phase)) # define HALF_OF(x) ((x)>>1) #elif defined(USE_SIMD) -# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) ) -# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) ) -# define HALF_OF(x) ((x)*_mm_set1_ps(.5)) +# ifdef USE_SIMD_SIMDE +# define KISS_FFT_COS(phase) simde_mm_set1_ps( cos(phase) ) +# define KISS_FFT_SIN(phase) simde_mm_set1_ps( sin(phase) ) +# define HALF_OF(x) ((x)*simde_mm_set1_ps(.5f)) +# else +# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) ) +# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) ) +# define HALF_OF(x) ((x)*_mm_set1_ps(.5f)) +# endif #else # define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase) # define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase) diff --git a/kiss_fft.h b/kiss_fft.h index dce1034..008c357 100644 --- a/kiss_fft.h +++ b/kiss_fft.h @@ -48,15 +48,45 @@ extern "C" { /* User may override KISS_FFT_MALLOC and/or KISS_FFT_FREE. */ #ifdef USE_SIMD -# include -# define kiss_fft_scalar __m128 -# ifndef KISS_FFT_MALLOC -# define KISS_FFT_MALLOC(nbytes) _mm_malloc(nbytes,16) -# define KISS_FFT_ALIGN_CHECK(ptr) +# ifdef USE_SIMD_SIMDE +# include +# define kiss_fft_scalar simde__m128 +# define KISS_FFT_SET1_PS(x) simde_mm_set1_ps(x) +# ifndef KISS_FFT_MALLOC +# include +# if defined(_WIN32) +# define KISS_FFT_MALLOC(nbytes) _aligned_malloc(nbytes,16) +# define KISS_FFT_FREE _aligned_free +# elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) || \ + (defined(__cplusplus) && __cplusplus >= 201703L) + /* C11 aligned_alloc or C++17 */ +# define KISS_FFT_MALLOC(nbytes) aligned_alloc(16, ((nbytes) + 15) & ~(size_t)15) +# define KISS_FFT_FREE free +# else + /* POSIX posix_memalign */ + static inline void* kiss_fft_simde_malloc(size_t nbytes) { + void* ptr = NULL; + if (posix_memalign(&ptr, 16, nbytes) != 0) return NULL; + return ptr; + } +# define KISS_FFT_MALLOC(nbytes) kiss_fft_simde_malloc(nbytes) +# define KISS_FFT_FREE free +# endif +# endif +# define KISS_FFT_ALIGN_CHECK(ptr) # define KISS_FFT_ALIGN_SIZE_UP(size) ((size + 15UL) & ~0xFUL) -# endif -# ifndef KISS_FFT_FREE -# define KISS_FFT_FREE _mm_free +# else +# include +# define kiss_fft_scalar __m128 +# define KISS_FFT_SET1_PS(x) _mm_set1_ps(x) +# ifndef KISS_FFT_MALLOC +# define KISS_FFT_MALLOC(nbytes) _mm_malloc(nbytes,16) +# define KISS_FFT_ALIGN_CHECK(ptr) +# define KISS_FFT_ALIGN_SIZE_UP(size) ((size + 15UL) & ~0xFUL) +# endif +# ifndef KISS_FFT_FREE +# define KISS_FFT_FREE _mm_free +# endif # endif #else # define KISS_FFT_ALIGN_CHECK(ptr) diff --git a/kiss_fftr.c b/kiss_fftr.c index 778a9a6..4a20fa1 100644 --- a/kiss_fftr.c +++ b/kiss_fftr.c @@ -93,7 +93,7 @@ void kiss_fftr(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *fr freqdata[0].r = tdc.r + tdc.i; freqdata[ncfft].r = tdc.r - tdc.i; #ifdef USE_SIMD - freqdata[ncfft].i = freqdata[0].i = _mm_set1_ps(0); + freqdata[ncfft].i = freqdata[0].i = KISS_FFT_SET1_PS(0); #else freqdata[ncfft].i = freqdata[0].i = 0; #endif @@ -146,7 +146,7 @@ void kiss_fftri(kiss_fftr_cfg st,const kiss_fft_cpx *freqdata,kiss_fft_scalar *t C_ADD (st->tmpbuf[k], fek, fok); C_SUB (st->tmpbuf[ncfft - k], fek, fok); #ifdef USE_SIMD - st->tmpbuf[ncfft - k].i *= _mm_set1_ps(-1.0); + st->tmpbuf[ncfft - k].i *= KISS_FFT_SET1_PS(-1.0); #else st->tmpbuf[ncfft - k].i *= -1; #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4d07fb9..a83d12d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -46,10 +46,12 @@ add_kissfft_test_executable(testcpp testcpp.cc) if(KISSFFT_DATATYPE MATCHES "^simd$") add_kissfft_test_executable(tsimd test_simd.c) target_compile_definitions(tsimd PRIVATE USE_SIMD) - if (NOT MSVC) - target_compile_options(kissfft PRIVATE -msse) - else() - target_compile_options(kissfft PRIVATE "/arch:SSE") + if(NOT KISSFFT_USE_SIMDE AND KISSFFT_X86) + if (NOT MSVC) + target_compile_options(tsimd PRIVATE -msse) + else() + target_compile_options(tsimd PRIVATE "/arch:SSE") + endif() endif() endif() diff --git a/test/Makefile b/test/Makefile index 945250f..18a9c62 100644 --- a/test/Makefile +++ b/test/Makefile @@ -138,7 +138,7 @@ endif $(TESTSIMD): test_simd.c ifeq "$(KISSFFT_DATATYPE)" "simd" - $(CC) -o $@ -g $(CFLAGS) -DUSE_SIMD=1 -msse $< -L.. -l$(KISSFFTLIB_SHORTNAME) -lm + $(CC) -o $@ -g $(CFLAGS) $(TYPEFLAGS) $< -L.. -l$(KISSFFTLIB_SHORTNAME) -lm else $(error ERROR: This test makes sense only with KISSFFT_DATATYPE=simd) endif diff --git a/test/test_real.c b/test/test_real.c index 9e4bd58..e40df6f 100644 --- a/test/test_real.c +++ b/test/test_real.c @@ -22,7 +22,7 @@ static kiss_fft_scalar rand_scalar(void) { #ifdef USE_SIMD - return _mm_set1_ps(rand()-RAND_MAX/2); + return KISS_FFT_SET1_PS(rand()-RAND_MAX/2); #else kiss_fft_scalar s = (kiss_fft_scalar)(rand() -RAND_MAX/2); return s/2; diff --git a/test/test_simd.c b/test/test_simd.c index d9c6790..de89a6a 100644 --- a/test/test_simd.c +++ b/test/test_simd.c @@ -6,16 +6,16 @@ static void test1(void) int n[2] = {256,256}; size_t nbytes = sizeof(kiss_fft_cpx)*n[0]*n[1]; - kiss_fft_cpx * inbuf = _mm_malloc(nbytes,16); - kiss_fft_cpx * outbuf = _mm_malloc(nbytes,16); + kiss_fft_cpx * inbuf = KISS_FFT_MALLOC(nbytes); + kiss_fft_cpx * outbuf = KISS_FFT_MALLOC(nbytes); memset(inbuf,0,nbytes); memset(outbuf,0,nbytes); kiss_fftnd_cfg cfg = kiss_fftnd_alloc(n,2,is_inverse,0,0); kiss_fftnd(cfg,inbuf,outbuf); kiss_fft_free(cfg); - _mm_free(inbuf); - _mm_free(outbuf); + KISS_FFT_FREE(inbuf); + KISS_FFT_FREE(outbuf); } int main(void) diff --git a/test/twotonetest.c b/test/twotonetest.c index 5f08daf..97edbf6 100644 --- a/test/twotonetest.c +++ b/test/twotonetest.c @@ -38,7 +38,7 @@ double two_tone_test( int nfft, int bin1,int bin2) /* generate a signal with two tones*/ for (i = 0; i < nfft; i++) { #ifdef USE_SIMD - tbuf[i] = _mm_set1_ps( (maxrange>>1)*cos(f1*i) + tbuf[i] = KISS_FFT_SET1_PS( (maxrange>>1)*cos(f1*i) + (maxrange>>1)*cos(f2*i) ); #else tbuf[i] = (maxrange>>1)*cos(f1*i) diff --git a/tools/kiss_fastfir.c b/tools/kiss_fastfir.c index d4e666c..d2cd65e 100644 --- a/tools/kiss_fastfir.c +++ b/tools/kiss_fastfir.c @@ -154,8 +154,8 @@ kiss_fastfir_cfg kiss_fastfir_alloc( for ( i=0; i < st->n_freq_bins; ++i ) { #ifdef USE_SIMD - st->fir_freq_resp[i].r *= _mm_set1_ps(scale); - st->fir_freq_resp[i].i *= _mm_set1_ps(scale); + st->fir_freq_resp[i].r *= KISS_FFT_SET1_PS(scale); + st->fir_freq_resp[i].i *= KISS_FFT_SET1_PS(scale); #else st->fir_freq_resp[i].r *= scale; st->fir_freq_resp[i].i *= scale; @@ -286,7 +286,7 @@ void direct_file_filter( tmph = imp_resp+nlag; #ifdef REAL_FASTFIR # ifdef USE_SIMD - outval = _mm_set1_ps(0); + outval = KISS_FFT_SET1_PS(0); #else outval = 0; #endif @@ -297,7 +297,7 @@ void direct_file_filter( outval += buf[k] * *tmph; #else # ifdef USE_SIMD - outval.r = outval.i = _mm_set1_ps(0); + outval.r = outval.i = KISS_FFT_SET1_PS(0); #else outval.r = outval.i = 0; #endif