Skip to main content

IPP libraries in Cygwin

The Intel Integrated Performance Primitives (IPP) libraries are excellent optimized libraries for Intel architectures that can really boost the speed of complex operations. Intel supplies them for Windows, Linux, and Mac OSX, and they also include optimizations specific to each Intel processor architecture. However, you should be cautious when using them in a Cygwin environment; they will work just fine, but the "default" usage won't work as expected.

On Linux systems (and Mac OSX I believe, since they have a common ancestor in unix), the normal calling convention for library functions is the cdecl calling convention. In this calling convention, all arguments to a function are pushed on the stack, and the return value from the function can be found in EAX. There are also some registers which, if in use, must be saved by the caller (EAX, ECX, and EDX), whereas the others must be saved by the callee if the callee is going to use them. So that is all well and good.

However in Windows, there are two calling conventions that are regularly used: cdecl and the Windows-specific stdcall. stdcall differs in that the callee is responsible for cleaning up the stack arguments before returning, instead of the caller as is done in cdecl. Otherwise, the calling convention is the same, and even has the same callee- and caller-saved registers.

The Windows static IPP libraries provided by Intel only have stdcall symbols, I think (the symbol name specifies the calling convention), however the DLL's have cdecl symbols.

The Cygwin version of gcc can link directly to DLL's and generate the appropriate code at runtime, whereas normally in Windows programming you'd link to what's called an import library which takes care of loading the DLL, and has symbols for each symbol in the DLL.

The tricky thing with this is that if you simply include ipp.h and link your program with the DLL's:

$ gcc ippcore-version.dll ippcv-version.dll program.c -o program

or

$ gcc program.c -o program -lippcore-version -lippcv-version

everything will link just fine since the DLL's have cdecl symbols.

BUT, the implementation of the Windows functions in the IPP DLL's follow the stdcall calling convention! So with this you just made a ticking time bomb. The worst part is, because stdcall and cdecl are so similar, there are actually lots of situations where your program will run without error! However, with sufficiently complicated code, your program will encounter strange and often difficult to debug issues, since the stack pointer is not correct, which will allow the stack to be corrupted. In this case, the IPP functions are winding the stack back up, but then the calling code will also wind the stack back up. So if you happen to get lucky and your code doesn't use the stack after calling the IPP functions, you can return from a function and all will be well (since EBP will be used to restore ESP). In optimized code this is actually more likely, since optimized code is less likely to push and pop EBP on the stack, among other things. Otherwise, very odd behavior will occur, and usually it will end in a segmentation fault (since the stack frames are often going to get messed up entirely).

The solution to this issue is actually quite simple. The easiest way (in my opinion) to use the IPP libraries is to link to the DLL's using gcc, so we need to tell gcc that the IPP functions will use the stdcall calling convention, even though the symbols of the DLL don't indicate this.

If you read through ippdefs.h you'll see that at the top they have something along the lines of

#if defined(_WIN32) || defined(_WIN64)
#define CDECL   __cdecl
#define STDCALL __stdcall
#define INT64   __int64
#else
#define CDECL
#define STDCALL
#define INT64   long long
#endif

The Cygwin code is actually falling into the "else" designation here; Cygwin gcc doesn't by default define _WIN32 or _WIN64. We DO want to use the upper set of defines, though, since the IPP library header files actually take into account the calling conventions based on these defines. So all we need to do is pass a few new flags on the compile and linking lines:

$ gcc -c -D_WIN32 -D__int64="long long" -o program.o program.c
$ gcc -Wl,--enable-stdcall-fixup -o program program.o \
      -lippcore-version -l[other ipp libraries]

Here we define the _WIN32 macro (we could also do _WIN64) and __int64 as a macro (because that is a Windows-specific value that doesn't exist in the Cygwin compilation environment) when compiling code. Then, when linking code, you can optionally include -Wl,--enable-stdcall-fixup to tell the linker to not warn us that it is linking the stdcall symbols (which are now being added to the object file from the IPP header files) to the cdecl symbols in the DLL's. If you don't pass this flag, it'll just print warnings for each symbol that it strips the stdcall suffix from to link it to the matching DLL symbol.

Note that for the compilation step, instead of using -D_WIN32 you may pass -mwin32 instead; this will define that macro and a couple others.

And now the code won't crash anymore, and you can use IPP as you normally would!

Comments