CMake macOS Universal Binary build: How to link to a target for x86_64 only?

Probably more a general CMake question than a JUCE-specific one, but maybe relevant for others round here as well… I’m currently trying to build a macOS arm/x86_64 universal binary plugin with JUCE 6 for the first time. We use IPP a lot for our current plugins and now built some replacement solutions targeting arm.

So now, what I do is specifying

set(CMAKE_OSX_ARCHITECTURES arm64 x86_64) at the top of the CMakeLists and according to lipo -archs this generates a universal binary :tada: But I see a lot of linker warnings that the IPP libraries have only been linked to the x86_64 target – which is quite obvious. So now I’m wondering what’s the correct solution to only link to IPP for x86? I tried the following approach

add_library(ALL_IPP INTERFACE)

set(IPP_ROOT "/opt/intel/ipp")

target_include_directories(ALL_IPP INTERFACE "${IPP_ROOT}/include")

add_library(ippi    STATIC IMPORTED GLOBAL)
add_library(ipps    STATIC IMPORTED GLOBAL)
add_library(ippvm   STATIC IMPORTED GLOBAL)
add_library(ippcore STATIC IMPORTED GLOBAL)

set (IPP_LIB "${IPP_ROOT}/lib")

set_target_properties(ippi    PROPERTIES IMPORTED_LOCATION ${IPP_LIB}/libippi.a    CMAKE_OSX_ARCHITECTURES x86_64)
set_target_properties(ipps    PROPERTIES IMPORTED_LOCATION ${IPP_LIB}/libipps.a    CMAKE_OSX_ARCHITECTURES x86_64)
set_target_properties(ippvm   PROPERTIES IMPORTED_LOCATION ${IPP_LIB}/libippvm.a   CMAKE_OSX_ARCHITECTURES x86_64)
set_target_properties(ippcore PROPERTIES IMPORTED_LOCATION ${IPP_LIB}/libippcore.a CMAKE_OSX_ARCHITECTURES x86_64)

target_link_libraries(ALL_IPP INTERFACE ippi ipps ippvm ippcore)

# somewhere later
target_link_libraries(myPlugin PRIVATE ALL_IPP)

But I still get errors like

ld: warning: ignoring file /opt/intel/ipp/lib/libippi.a, building for macOS-arm64 but attempting to link with file built for unknown-x86_64

So that doesn’t seem to be the right approach. But what’s the right approach? :slight_smile:

Interested in this as well.

We had the issue trying to move an iOS project to cmake and didn’t found a way to setup different lib folder for each architecture like you can for debug/release

Alright, in the end I found a (stupid) workaround :wink: I created an empty dummy library project with arm64 set as target architecture only and join this with each of the ipp libraries needed by the lipo tool. Then I link to that libraries. Feels a bit hacky, but seems to work for now :smiley:

# Some library containing an empty function. Simple purpose is to have an empty arm64 lib to join with the real x86_64 libs
add_library(ippARMDummy ippARMDummy.c)
set_target_properties(ippARMDummy PROPERTIES OSX_ARCHITECTURES arm64)

set(IPP_ROOT "/opt/intel/ipp")
set(IPP_LIB "${IPP_ROOT}/lib")

add_custom_target(ippiMultiarch
        COMMAND lipo -create libippArmDummy.a ${IPP_LIB}/libippi.a -output libippiMultiarch.a
        DEPENDS ippARMDummy)

add_custom_target(ippsMultiarch
        COMMAND lipo -create libippArmDummy.a ${IPP_LIB}/libipps.a -output libippsMultiarch.a
        DEPENDS ippARMDummy)

add_custom_target(ippvmMultiarch
        COMMAND lipo -create libippArmDummy.a ${IPP_LIB}/libippvm.a -output libippvmMultiarch.a
        DEPENDS ippARMDummy)

add_custom_target(ippcoreMultiarch
        COMMAND lipo -create libippArmDummy.a ${IPP_LIB}/libippcore.a -output libippcoreMultiarch.a
        DEPENDS ippARMDummy)

add_custom_target(ippMultiarch_All DEPENDS ippiMultiarch ippsMultiarch ippvmMultiarch ippcoreMultiarch)
3 Likes

Sorry to drag this up again…
Was this the solution you went with?

When we went Universal Binary, I transitioned our entire codebase from IPP to vDSP. But this seems to be causing significant issues/slowdown on our Intel slice.

Yes, this is the solution that we are going with until today and it works well.

I can also confirm your findings that IPP seems to outperform vDSP on Intel in most cases – especially with FFTs which we benchmarked a bit.

We transitioned to wrap nearly all our vector math work (FFT left out for now) behind our VCTR library which attempts to chose IPP based functions as the first choice to evaluate a vector math operation, which result in quite satisfying results overall, although I’m still planing to do a function by function benchmark comparison between both at some point.

Use the JUCE_INTEL macro to conditionally include and use IPP.

vDSP indeed is much slower than IPP. I assume there is a reason why it performs so bad on Intel! :frowning:

While this will build and work, this leaves you with linker warnings since you don’t only have to include the header but have to link the IPP library to your universal binary plugin or application. You can’t link something conditionally to a single architecture slice to my knowledge, which results in linker warnings like I mentioned in my initial post back then. You can of course just accept to ignore these warnings, but we all aim for warning free builds at all cost, don’t we? The solution shared above results in a warning free linkage.

It works like a charm in my products - do you mean you dropped IPP for this?

So you say that you can link a universal binary with the original IPP static library on mac and don’t get any linker warning like

ld: warning: ignoring file /opt/intel/ipp/lib/libippi.a, building for macOS-arm64 but attempting to link with file built for unknown-x86_64

during build, do I get you right? This would be quite interesting. In that case, may I ask which build toolchain you are using?

Just to make that clear: Even when this warning is emitted, you will create a binary that runs without any runtime issues and happily uses IPP in the Intel slice. This is purely about a warning free build output.

No, we still use it and prefer to use it over vDSP as I said. We modified the intel libraries as explained above by adding an empty ARM slice to them using the lipo tool so that they become a somewhat fake universal binary, which satisfied the linker enough to not emit any warning any longer. At least that was the case 3 years ago, we didn’t update the IPP libraries since then.

I use the Projucer, also in build scripts, and I simply refer to IPP static libs that are not found for the Silicon build. That gives a warning message that is totally OK with me, because for the Silicon slice there is no code referring to it. A very simple approach, no further need for lipo etc.

For Silicon I conditionally include another FFT library, vDSP FFT is one of the slowest.

I think that is the difference here, we have a company internal policy where we enforce warning free builds, so for us this warning is not OK because we aim at having builds that don’t output a single warning. Therefore the solution posted above – which by the way can also be easily be transitioned to a non-CMake context – is the solution to simply silence the warning. But I totally get that one can decide to happily ignore that warning since it will never cause any real-world issues.

1 Like

After nearly 40 years in software development teams, I am so lucky that I made a switch three years ago to have those policy discussions with myself now… :wink:
And trust me, I am still a quality and standards freak

Interesting, my experience is different from both of yours! I could use some help, maybe I’m missing something obvious.

Yesterday I reimported IPP to my project and carefully wrapped all IPP functionality in JUCE_INTEL macros just like Peter said. Some refactoring was done since we moved away from IPP but I don’t think it should result in what’s happening to me.

Now I get the same (similar) warning:
Ignoring file /opt/intel/ipp/lib/libippi.a, file is universal (x86_64,i386) but does not contain the arm64 architecture: /opt/intel/ipp/lib/libippi.a

PLUS a long list of errors:

Undefined symbols for architecture arm64:
  "_ippsDFTInv_CToC_32f", referenced from:
      FILTER::do_FFT_filter(float*, int) in lto.o
  "_ippsMulC_32f_I", referenced from:
      FILTER::do_FFT_filter(float*, int) in lto.o

…etc followed by:

ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

I don’t get errors when I build exclusively for Intel, or when I git stash all my IPP code. But I DO get errors any time I build Universal Binary with my JUCE_INTEL defs!

Any thoughts on what could be going on?

P.S @PluginPenguin the VCTR library seems super useful, I should play around with that! Not for FFT yet though? :slight_smile:

You still have references to IPP symbols in your Silicon build.

Do you have an alternative DFT/FFT to use in this build?

Like in (from my code):

void FFT::forwardFftRtoC (float* srcDest) noexcept
{
    #if JUCE_INTEL

        ippsFFTFwd_RToCCS_32f_I (srcDest, specPtr, workBuffer.get ());

    #else

        // call to the corresponding FFT function for Silicon

    #endif
}

The linker error clearly states that it could not find symbols for the ARM slice, this means that in your code you still have unconditional references to IPP functions that are used even when building for ARM. The more detailed message above should already give you quite a lot of hints where to look. It seems that you have a function somewhat like that

FILTER::do_FFT_filter (float* srcDest, int len)
{
    
}

which accidentally calls the IPP functions ippsDFTInv_CToC_32f and ippsMulC_32f_I also for the ARM build. Note that depending on the optimisation level you are building with, these function can also be called in sub-function calls inside your do_FFT_filter function, the compiler might have inlined it so that the linker only knows the do_FFT_filter as a reference.

Yeah, give it a go, however I must admit that the current readme and especially the examples are completely outdated, so feel free to hit me up if you need help integrating it or have a look at the little demo I gave during my last ADC talk to get a basic idea of how to use it. FFT is on the list but not part of the current feature set. However, if you are really interested let’s better discuss that in a separate thread and stay focused on your main issue here :wink:

Thanks for the response both of you, seems like this is the case. Hopefully I’ll find those lurking references soon…

EDIT: Actually I don’t think this is the issue because of one thing I mentioned: When I comment out code between JUCE_INTEL defs, it runs fine. So I think something’s wrong with my preprocessor defs. Will update when I figure it out…

EDIT: I figured it out! I didn’t want to complicate things so I didn’t say, but I was using a “USE_IPP” custom preprocessor def, which was defined by an earlier JUCE_INTEL preprocessor def. I did a ‘find and replace’ on all JUCE_INTEL instances and now it seems fixed, so something was off in the redundancy.

Thanks so much, particularly @PluginPenguin, you’ve helped me out so many times now :sweat_smile:

1 Like

It looks like accommodating with the link warnings is no longer an option with Xcode 15… a huge amount of warnings is produced by their new linker and it makes Xcode hang really bad…

I would have loved a proper solution within the Projucer, with linker flags per architecture… but in the meantime @PluginPenguin’s solution is a good workaround :+1: Thanks for sharing

1 Like

Thanks for this tip @PluginPenguin

For those who use the Projucer+Xcode, here is a script to create such dummy UniversalBinary libraries:

#!/bin/bash

echo "void dummyFunction(){}" >ippARMDummy.c
clang -c  -target arm64-apple-macos10.13 -o ippARMDummy.o ippARMDummy.c 
ar r  ippARMDummy.a ippARMDummy.o

IPP_LIB=/opt/intel/ipp/lib
lipo -create ippARMDummy.a ${IPP_LIB}/libippi.a -output libippiMultiarch.a
lipo -create ippARMDummy.a ${IPP_LIB}/libipps.a -output libippsMultiarch.a
lipo -create ippARMDummy.a ${IPP_LIB}/libippvm.a -output libippvmMultiarch.a
lipo -create ippARMDummy.a ${IPP_LIB}/libippcore.a -output libippcoreMultiarch.a

Including these “multiarch”-libraries successfully removed the 52360 warnings messages from arm64-linking phase.

However I get still 866 warnings while linking for x86_64 in Xcode 15

No platform load command found in '/Users/admin/dev/ipp/libippsMultiarch.a[x86_64][2](pscmndft_bitrevm7as_k0.o)', assuming: macOS
No platform load command found in '/Users/admin/dev/ipp/libippsMultiarch.a[x86_64][13](pscmndft_dftsdirm7as_k0.o)', assuming: macOS
No platform load command found in '/Users/admin/dev/ipp/libippsMultiarch.a[x86_64][129](transfer_ipp_mkl_error_k0---cmn_dft_avx512_transfer_ipp_mkl_error.o)', assuming: macOS

If anyone has a tip? I’m using the latest IPP Version 2021.9