kfrlib / kfr

Fast, modern C++ DSP framework, FFT, Sample Rate Conversion, FIR/IIR/Biquad Filters (SSE, AVX, AVX-512, ARM NEON)
https://www.kfrlib.com
GNU General Public License v2.0
1.66k stars 253 forks source link

Unable to properly link kfr_dft #202

Closed Zaraka closed 10 months ago

Zaraka commented 10 months ago

While evaluating this library I ran into an odd issue. I have compiled the kfr library with DFT, tests, and examples enabled. All Tests and examples work great. I then proceed to install the kfr library as normal, resulting in kfr_dft.a and kfr_io.a in my local/lib folder.

But If I try to copy/paste dft example into my separate qmake/cmake project and then linking the library as normal like this

target_link_libraries(apptestKFR
    PRIVATE Qt6::Quick
    kfr_dft
)

It does not compile and crash on the error

/usr/bin/ld: CMakeFiles/apptestKFR.dir/main.cpp.o: in function `kfr::dft_plan<double>::dft_plan(kfr::cpu_t, unsigned long, kfr::dft_order)':
/usr/local/include/kfr/dft/fft.hpp:166: undefined reference to `void kfr::sse2::dft_initialize<double>(kfr::dft_plan<double>&)'
collect2: error: ld returned 1 exit status

While digging in the fft.htpp I noticed KFR_DFT_MULTI macro which was not defined anywhere. So I just try to define it before including

#define KFR_DFT_MULTI
#include <kfr/base.hpp>
#include <kfr/dft.hpp>
#include <kfr/dsp.hpp>
#include <kfr/io.hpp>

and the binary started successfully linking against kfr_dft but dft example still does not work, because dft_plan is not properly initialized and it crashes on execute. Backtrace here

I seriously do not understand whether I compiled or installed kfr incorrectly. Would appreciate any help

dancazarin commented 10 months ago

You have compiled and installed kfr successfully but cannot link it to an external project, right?

Could you try to add this include before any other includes in the source file where you use kfr functions.

#include <kfr/config.h>

config.h is generated as part of install process and should reside in your local/include directory.

Also please provide more information about your environment. OS and compiler used (with version). C++ flags and options (if not standard)

KFR 6 ( #196 ) will include prebuilt binaries ready for using with Windows, linux and macOS. This will also include Clang-compiled DFT for multiple architectures (selected at runtime).

Zaraka commented 10 months ago

I have tried including kfr/config.h as you requested. No changes. My config.h file contains only

#define KFR_DFT_NO_NPo2

Yes the installation succeded without any problems. Here is my CMakeCache.txt Installation is fine

/usr/local/lib$ ls
libkfr_dft.a libkfr_io.a

This is on Fedora 39, x86_64c++ (GCC) 13.2.1 20231011 This kfr copy is from current main 8d14765b988ba3d95476b5490d966ccaea7ae0c7 I'm linking with -lkfr_dft using QMake, but I also tried a test CMake project with

add_subdirectory(kfr)

target_link_libraries(apptestKFR
    PRIVATE Qt6::Quick
    kfr kfr_dft
)

same result. but I'm not that versed with CMake I maybe made a mistake somewhere. I'm not sure if is KFR even supported with GCC/MSVC. I can compile it with Clang if that is not the case, but I always thought linking Clang-compiled libraries with GCC/MSVC could get you unexpected results.

Zaraka commented 10 months ago

Just to test this out, I have tried also compiling KFR with Clang and then linking it with GCC, same result. I'll wait for KFR 6 and try again.

dancazarin commented 10 months ago

Linking GCC-compiled and Clang-compiled static libraries is perfectly fine as long as the both libraries have been built using the same C++ library (GCC's libstdc++ for Linux). The same applies for MSVC-compiled and Clang-compiled binaries (both should use Microsoft's STL). This is like Chrome/Chromium project is built by Google so it's battle-proven approach and shouldn't cause any issues.

As for linking ussue in KFR. There are two ways to use KFR. Use one architecture everywhere (but it must match in static library and your project) or use multiple architectures (selected at runtime).

1. Single architecture

Use -DKFR_ARCH=avx2 (or avx or sse41 or even sse2) when you run CMake to install KFR. Example:

cmake -GNinja -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release -DKFR_ARCH=avx2 ..
ninja
sudo ninja install # This installs libkfr_dft.a

The same architecture should be used when building your source files: Example:

g++ -mavx2 ... your_source.cpp -lkfr_dft

Then linking works well and KFR will use avx2 instructions for both DFT and other functions (built in your code).

Code from examples/dft.cpp will show:

KFR 5.1.0 avx2 64-bit (gcc-11.4.0/linux) +in
fft_specialization<double, 7>(avx2): 0, 128, 3072, 0, 1, 0, 0, 0, 1, 0, 0

2. Multiple architectures.

First please update KFR using dev branch for small GCC compatibility fix.

Setting KFR_ENABLE_DFT_MULTIARCH to ON enables multiple architectures. In this case instead of a single libkfr_dft.a multiple arch-specific libraries will be installed.

cmake -GNinja -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release -DKFR_ENABLE_DFT_MULTIARCH=ON ..
ninja
sudo ninja install # This installs libkfr_dft_sse2.a libkfr_dft_sse41.a libkfr_dft_avx.a libkfr_dft_avx2.a libkfr_dft_avx512.a

Then you can compile your code using any architecture settings but should link all KFR DFT libraries: Example (gcc will select sse2 for your_source.cpp):

g++ your_source.cpp -Wl,--push-state,--whole-archive -lkfr_dft_sse2 -Wl,--pop-state -lkfr_dft_sse41 -lkfr_dft_avx -lkfr_dft_avx2 -lkfr_dft_avx512

(Update: whole-archive flag is needed to link inline and template functions with correct architecture)

KFR code will detect cpu at runtime and select appropriate code path for DFT.

Code from examples/dft.cpp will show:

KFR 5.1.0 sse2 64-bit (gcc-11.4.0/linux) +in
fft_specialization<double, 7>(avx2): 0, 128, 3072, 0, 1, 0, 0, 0, 1, 0, 0

Notice that first mentioned architecture is sse2 (architecture used for your_source.cpp) while the second is now avx2 (dft source selected at runtime)

Hope this helps and let me know you have more questions.

dancazarin commented 10 months ago

@Zaraka DFT should be compiled using Clang anyway because the code that GCC generates for DFT is much slower. DFT performance comparison published for KFR was made using Clang and only with Clang we guarantee best performance. This won't affect your workflow because Clang should be used only for building static libraries. After building KFR libraries you are free to use any compiler and all should work ok. This applies for both KFR 5 and KFR 6.

The issue you mentioned could be easily fixed by specifying correct flags right now in KFR 5.

dancazarin commented 10 months ago

@Zaraka Is your problem resolved with the instructions above? If yes, I'll close the issue here.

Zaraka commented 10 months ago

@Zaraka Is your problem resolved with the instructions above? If yes, I'll close the issue here.

I'm sorry for the late answer I was very overburdened with other work. I have tested the latest commit from main/dev KFR 5.2.0, compiled with Clang as you requested with the linker flags you specified, linking succeeded. I will close this issue once I test it on the Windows platform as well, which should be today/tomorrow.

dancazarin commented 10 months ago

For Windows build instructions are similar but below are exact commands.

1. Single architecture (simpler setup)

:: Warning: VS Path and LLVM Path may be different on your machine
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
cmake -GNinja -DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe" -DCMAKE_LINKER="C:/Program Files/LLVM/bin/lld-link.exe" -DCMAKE_BUILD_TYPE=Release -DKFR_ARCH=avx2 -DCMAKE_INSTALL_PREFIX=install ..
ninja
ninja install # This installs kfr_dft.lib to CMAKE_BINARY_DIR/install

Then, the following compile options must be added (through VS Project Properties or CMake target_compile_options)

/arch:AVX2 "PATH-TO-INSTALLED-KFR/lib/kfr_dft.lib"

As always /arch must match KFR_ARCH argument in CMake call.

2. Multiple architectures (best performance)

:: Warning: VS Path and LLVM Path may be different on your machine
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
cmake -GNinja -DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe" -DCMAKE_LINKER="C:/Program Files/LLVM/bin/lld-link.exe" -DCMAKE_BUILD_TYPE=Release -DKFR_ENABLE_DFT_MULTIARCH=ON -DCMAKE_INSTALL_PREFIX=install ..
ninja
ninja install # This installs kfr_dft_sse2.lib kfr_dft_sse41.lib kfr_dft_avx.lib kfr_dft_avx2.lib kfr_dft_avx512.lib to CMAKE_BINARY_DIR/install

KFR_ENABLE_DFT_MULTIARCH=ON is the key option here.

/WHOLEARCHIVE:"PATH-TO-INSTALLED-KFR/lib/kfr_dft_sse2.lib" "PATH-TO-INSTALLED-KFR/lib/kfr_dft_sse41.lib" "PATH-TO-INSTALLED-KFR/lib/kfr_dft_avx.lib" "PATH-TO-INSTALLED-KFR/lib/kfr_dft_avx2.lib" "PATH-TO-INSTALLED-KFR/lib/kfr_dft_avx512.lib"

/WHOLEARCHIVE for sse2 lib is required here for the same reason as linux code has --whole-archive: to force compiler to select correct inline/template functions from multiple similar libraries. Without this a runtime invalid instruction exception may occur.

Zaraka commented 10 months ago

I followed the instructions as requested and got everything in working order. Thank you very much for your help, I would strongly suggest writing this kind of information into ReadMe.md or Installation.md as I was previously unable to find this exact information anywhere in Documentation.