November 8th, 2024

Which compilation target should I pick for my Arm64 music apps, ASIO components, and plugins?

Pete Brown
Principal Software Engineer

The Windows music creation community today is centered around Intel/AMD x64 (x86-64) because that is what most Windows PCs run for some time now. The introduction of Arm64 Copilot+ PCs has provided new opportunities with efficient and powerful devices with great battery life and NPUs. But because we continue to support both the AMD/Intel x86-64 (x64) and Arm64 devices and instruction sets, and are not forcing a move to one or the other, developers need to make some decisions about how to target their code to maximize compatibility and minimize development and support work.

Windows 11 includes x64 emulation built-in on Arm64 devices. This emulation can be run for entire processes, or for specific functions within a process. Arm64EC is the technology which enables mixing and matching within a process.

I’ve written about Arm64EC in the past. In “Arm64EC”, the “EC” is “Emulation Compatible” Arm64 native code, and provides almost all of the benefits of 100% native classic Arm64 target, including access to native instructions and the NPU, and the ability to dynamically or statically link to other dependencies that have not yet been ported over to Arm. The x64 dependencies run under emulation, but are able to be loaded in the process of the host app.

Arm64EC is a Windows 11 C++ technology which requires MSBuild (or Visual Studio). It is not available for other languages or build systems unless you manually implement it.

About the Prism emulator

Windows 11, starting with the 24H2 release, includes a new emulator on Arm64 named “Prism”. This emulator has significant performance improvements over the older emulation technology. You can learn more about Prism here. The page also includes some clarifying information on detecting when your app is running under emulation, which is a common, but difficult question to answer at times.

Prism can be used for emulation of user-mode components. Emulation is not available for kernel-mode components like WDM drivers. These must be compiled to Arm64 only. If, for example, you are creating a custom ASIO driver, the kernel-mode component will need to be Arm64, but the user-mode ASIO DLL should be Arm64X, as detailed below.

If you are working on a music creation application today, which hosts third-party plugins for which there is already an ecosystem which your users have invested in, Arm64EC is the recommended target if you want to load those existing x64 plugins directly into your host process, without requiring an external host. It’s also what we recommend if you have other dependencies (.dll or .lib) which have not yet been ported over to Arm64.

Trade-offs with Arm64EC

Arm64EC does have some limitations and differences due to the x64 compatibility. You can read about those differences here.

In-particular, the register mapping and call checkers may require some thought for highly optimized code, and may impact your decision to go with Arm64 and an external host for plugins vs hosting in the main process. And, importantly, the ABI is not compatible with classic Arm64, only with other Arm64EC or x64 code.

Many apps are fine with those differences, and so Arm64EC will work well for them, and enable loading the existing x64 and new Arm64X/EC plugins directly into the host process. It also enables a gradual migration of other dependencies from x64 to Arm64(EC).

However, there will be developers who choose to compile the DAW to Arm64 due to the ABI differences, or because their toolchain does not support Arm64EC. Given that, will users have to install both Arm64 and Arm64EC versions of my plugins? What should plugin developers do to make this easier?

Enter Arm64X – The middleware/plugin choice

For application on Arm64 devices the choice really comes down to Arm64 or Arm64EC. For plugins, though, it’s a bit different. For these, we recommend Arm64X.

Arm64X is a combination of Arm64 and Arm64EC targets in the same file on disk. It’s an efficient “fat binary” for Arm64 PCs, and is a recommended approach for anyone developing middleware, plugins, and more. It’s how we ship our own in-proc system libraries on Arm64 devices. It’s also the compilation target for the Windows MIDI Services in-proc SDK components, and for other in-proc components like the new ASIO component of the low-latency UAC2 driver project.

Arm64X has one big advantage in that a single file contains both Arm64 and Arm64EC. This means that no file system redirection is required when loading DLLs that are shared between the two compilation targets, and in-proc COM object creation happens correctly as the clsid will ultimately point to a single file. This can be especially important when working with plugins, like VST, which expect that there will be only one copy of a given plugin in the shared plugin folder, and that plugin will be loadable by any calling application.

Here are the main options you have when implementing an application which can load every plugin of every compilation target. Read this in columns, with the app and its optional plugin host lined up to create the complete solution.

Image Arm64 Plugin Matrix

The separate plugin host process is entirely up to the developer to decide if appropriate and necessary for the application being developed. Some applications host all plugins in a separate process for security and crash resilience. And, of course, this does not consider or preclude off-machine plugin hosting like VEP and others.

Arm64X is required when you have:

  • A 64-bit COM server that may be called by both x64 or Arm64 apps
  • A plugin that may be loaded into either an x64 or Arm64 app
  • A single binary that gets injected into an x64 or Arm64 process

Many users will have a combination of plugin hosts on their Arm64 PC, with different ABIs. Releasing your in-proc components: plugins, ASIO DLLs, and more, as Arm64X will help ensure they all work correctly.

Pure forwarders

If you do not want to compile everything for Arm64X, you can create a pure forwarder DLL. This has a small Arm64X front-end, but points to the appropriate single-target Arm64 and Arm64EC binaries. This can sometimes be useful when you do not control the compilation of the binaries, such as with third-party dependencies, or you have a build system which does not use MSBuild.

You can learn more about Arm64X, and also about pure forwarders here.

How to identify the type of binary on Windows

With this matrix of binary types, and the compatibility provided by the emulator, it can sometimes be challenging to keep track of, or double-check, your dependencies at development and testing time. If you have a plugin or other PE binary in Windows, and need to see what it targets, developers can use the link tool.

link / dump /headers <binaryname>

The first set of headers returned will indicate Arm64, Arm64X, Arm64EC, or x64. An Arm64X binary will contain both Arm64 and Arm64X platforms, with the X here representing the optimized EC component. An Arm64-only binary without emulation compatibility will contain only Arm64 in the header. Here’s what you will see:

Arm64-only

    FILE HEADER VALUES 
        AA64 machine (ARM64)

Arm64X binary

    FILE HEADER VALUES
        AA64 machine (ARM64) (ARM64X)

Intel/AMD x64 binary

    FILE HEADER VALUES 
        8664 machine (x64)

Learn more

For additional FAQs on Arm64 Windows, please see this page.

Author

Pete Brown
Principal Software Engineer

Pete is a Principal Software Engineer in the Windows Developer Platform team Windows at Microsoft. He focuses on client-side dev on Windows, apps and technology for musicians, music app developers, and music hardware developers, and the Windows developer community. Pete is also the current chair of the Executive Board of the MIDI Association. He first got into programming and electronic music by working with sprites and the SID chip using BASIC on the Commodore 64 in 6th and 7th grade, and ...

More about author

0 comments