Arm64EC and plugins (post build step fails)

I was able to build with the plugins with CMake with the standard MS visual studio compiler for Arm64EC:

cmake -B cmake-build-arm64 -DCMAKE_PREFIX_PATH=../juce/install -DCMAKE_BUILD_TYPE=Release -A ARM64EC
cmake --build cmake-build-arm64 --config Release --verbose

But some of the later build steps fail after that with an error:

     if %errorlevel% neq 0 goto :VCEnd
     :VCEnd
     removing moduleinfo.json
 7>C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Microsoft\VC\v170\Microsoft.CppCommon.targets(166,5): error MSB3073: The c
   ommand "setlocal [C:\Daten\tal\tal-dub-x\cmake-build-arm64\AudioPlugin_VST3.vcxproj]
   C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Microsoft\VC\v170\Microsoft.CppCommon.targets(166,5): error MSB3073: "C:\P
   rogram Files\CMake\bin\cmake.exe" -E echo "removing moduleinfo.json" [C:\Daten\tal\tal-dub-x\cmake-build-arm64\AudioPlugin_VST3.vcxproj]
   C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Microsoft\VC\v170\Microsoft.CppCommon.targets(166,5): error MSB3073: if %e
   rrorlevel% neq 0 goto :cmEnd [C:\Daten\tal\tal-dub-x\cmake-build-arm64\AudioPlugin_VST3.vcxproj]

I think we all want to build for windows x64 and arm64 in the near future and it looks like ARM64EC is a simple and also user-friendly way to support this.

Can this be fixed or isn’t that a good solution at all? What is the best way to support arm64 for plugins?

Are you building on x86_64 or arm64?

I suspect the problem is that the plugin needs to be loaded and queried during the build in order to generate the moduleinfo.json file, but an arm64 plugin cannot be loaded on a x86_64 host. For the time being, you could build on arm64, or disable the manifest generation step (see the CMake API doc for details). We could also improve the error that is produced in this situation.

I see your point. I think Arm64ec (edit arm64X) should create one single binary containing the arm64 and the x64 build. Like the macOS universal binary. This is how I understand this. But I could also be wrong:

I understand that the postscript can’t run for both platforms on the same machine. At least not on a x64 machine. It would be great if that would work out of the box without any special CMake settings, choosing the right architecture for json generation.

I think it’s important that we choose a good solution here and avoid separate ARM builds when possible.

I’m not sure what way to go. There are so many options.

arm64ec is a separate ABI that is interoperable with x86_64 code. arm64x is different, and is a ā€˜fat’ binary format that includes both arm64 and arm64ec code. This is somewhat similar to the macOS universal binary format, but it still only runs on arm64 systems. I don’t think there’s a windows executable format that includes both x86_64 and arm64 code.

The current guidance seems to be that host apps should be distributed as x86_64 and arm64ec, while plugins should be x86_64 and arm64x.

Yes, we’ll need to do some work on our side to make cross-compiling a bit smoother.

Thanks for the info. I got that mixed up. arm64x looks like the one that works on all platforms for plugins. Would be great if we could build that directly with JUCE CMake.

Looking at this

https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/Locations+Format/Plugin+Format.html

I get the impression that if we are to add arm64 support for the new SnapDragon X CPUs running Windows 11, all we need is arm64ec and x86_64.
arm64x seems to be only needed to support older Windows on Arm systems and hosts built for those. Given the lack such hosts and the low number of systems sold, it seems mostly useless.

Steinberg appears to recommend arm64ec in the linked doc.

In the meantime, I don’t see the point of delivering an Arm64EC or Arm64X at all. We only make plugins that don’t host anything and don’t have any dependencies on x64 libraries.

I think the best choice for us is to have an arm64 and an x64 binary in the plugin package and always install both. The DAW should be able to choose the right one and prioritize the native plugin format.

Why should we do an arm64ec?

arm64ec is needed so x86_64 hosts running under Prism emulation can load the arm plugins and vice versa. It’s similar to what happened with UB2 on macOS.

On arm64 systems, most hosts will be built as arm64ec for maximum compatibility. These hosts can load x86_64 or arm64ec plugins, but cannot load arm64 plugins.

Running in such an environment, a plugin built as arm64ec is likely to perform better than a plugin built for x86_64 and emulated.

This is the relevant table from the link above, and I doubt anyone will build an Arm64 Classic host going forward.

Limitation for loading Plug-in DLL in a Host (inside the same process)

See Microsoft Blogs about this:

OS PC Architecture Host Process App Compatible Plug-in Architectures
Windows 10 Intel x86 Intel x86 Intel x86
Windows 10/11 Intel x64 Intel x64 Intel x64*
Windows 10/11 Intel x64 Intel x86 Intel x86
Windows 11 Arm64 Arm64 Classic Arm64 Classic, Arm64X
Windows 11 Arm64 Arm64EC Arm64X, Arm64EC, Intel x64
Windows 11 Arm64 Intel x64 Arm64EC, Intel x64, Arm64X

Better support for Windows Arm CMake has just landed in the 8.0.4 JUCE release

2 Likes

Ahh, I see. Didn’t notice that. Thought they can run also native Arm64 plugins. Then a Arm64Ec and a 64x it has to be…

Is there any time frame for when arm64ec support is going to be added to the Projucer? I tried adding it myself by appending to the arch lists in the msvc exporter, but I always end up with

#error "No global header file was included!"

Once I try to compile in the exported projects in Visual Studio.
With CMake things work great, but I have some older projects I’d like to migrate.

We’re hoping to have something available on the develop branch this week.

2 Likes

Awesome, thank you!

Thank you for the Projucer updates!
My special thanks for making it possible to have multiple archs/platforms in one visual studio config. :tada: :partying_face: :partying_face: :tada:

Testing this out I ran into an issue with my personal SIMD code that seems to be relevant for parts of JUCE as well:

Much to my surprise, when compiling for arm64ec, _M_X64 and _M_AMD64 are defined by msvc. This lead to issues for me as I assumed _M_AMD64 meant running x64_86 code and SSE2 support.

In JUCE there are also a few instances where _M_AMD64 is checked and some stuff looks a bit dodgy. It still compiles and there are struct compatibility reasons why MS decided to set _M_AMD64 for arm64ec:

Nevertheless, maybe it is worth looking into the spots revealed by searching for _M_AMD64 as currently the following defines are set for arm64ec, but not for arm64.
These might have some unintended side effects:

FLAC__CPU_X86_64 in libFLAC
ACF_CPU_INTEL inside ACF in the aax sdk (not relevant right now)
SMTG_CPU_X86_64 in fplatform.h in the VST3 sdk
1 Like

Out of interest what issue did you run into? My understanding is that at least in the vast majority of cases any amd64 / x86_64 instructions should work for am64ec. For intrinsics though there may be no corresponding equivalent so it can result in a warning, I might be wrong but I think it still ā€œworksā€ though.

A quick scan through the codebase suggests to me that SMTG_CPU_X86_64 (which is part of a fairly up to date VST3 SDK) and ACF_CPU_INTEL (part of the up to date AAX SDK) aren’t being used for much. FLAC__CPU_X86_64 could warrant a check though, thanks.

I just scanned the link you shared and that confirms what I thought, everything should still work. Maybe it’s worth us looking at updating our FLAC dependancy. Although it looks like we’re already pointing to the latest version according to the GitHub repo. Chances are we would hold off making any changes to it until there is new release, unless someone finds it’s causing a specific issue?

Thank you for looking into it. Yes, everything builds fine, but I couldn’t test flac yet. According to Microsoft it should indeed just work.

What made my builds fail was that I included ā€œemmintrin.hā€ when _M_AMD64 was defined - which resulted in a build failure. Reading the MS article again that actually seems a bit weird.

I also have my own fft wrapper which compiled its IPP variant based on the flag. That’s definitely not what I intended.

Additionally, the variant of pffft I use on arm64 did compile using SSE2 intrinsics instead of arm64 intrinsics because of _M_AMD64. While it still works, this seems not ideal.

Overall, I just thought it would be good to warn everybody about _M_AMD64, which might no longer do what you think once arm64ec is used.

2 Likes

I found another issue building for arm64ec on Windows 11. This one is crazy. I found a ā€œbugā€/incompatiblity in the arm neon intrinsics on Visual Studio 17.12.4, which might affect other plugin devs.

I’m using some hand-rolled arm64 neon code and to do absolute value/copysign/xorsign trickery I used a float constant of -0.f to get just the sign bit. This works fine in clang and also works correctly in debug builds on MSVC, but at the highest optimization settings + max inlining, vdupq_n_f32(-0.f) produces {0.f,0.f,0.f,0.f} instead of {-0.f,-0.f,-0.f,-0.f} - breaking my code in hilarious ways just on release builds only. Similar code works flawlessly using the same constant for SSE intrinsics btw…! The joys of early adoption!

Btw. Juce team, it would be very nice if the arm64ec config would be enabled for Projucer by default…

Are you using /fp:fast (MSVC) or -Ofast (Clang)? If so, the compiler may assume that -0.f is the same as 0.f because it may not follow IEEE-754 strictly. In fact, Clang 18.1.8 tells me that JUCE 8.0.6 itself shows a warning with -Ofast. See: