July 04, 2024
By Stefan Markovic, Jaime Bernardo, João Reis
Microsoft's move to ARM64 processors in their new devices is a game-changer. It's not just a hardware upgrade but a substantial shift impacting software compatibility and performance. With ARM64 architecture becoming more prevalent, the need for proficient software porting to this platform is critical. This transition is part of a broader industry trend, with many software companies and open source projects actively porting and optimizing their applications for ARM64 architecture, including Chromium, Adobe Creative Cloud apps (like Photoshop), Visual Studio Code, and many others.
Here at Janea Systems, we've been at the forefront of this wave, guiding businesses through the complexities of ARM64 porting. With a rich history of successful projects and deep technical expertise, we made this guide offering practical insights from real-world projects to help technical teams, product managers, and executives navigate the road to seamless ARM64-optimized software.
ARM64, also known as AArch64, isn't just a beefed-up version of ARM32. It's a whole new beast. ARM64 offers improvements that ARM32 and traditional x86/x64 architectures simply can't match, such as:
Emulating x86 and x64 on ARM64 might seem like a good idea until you realize it's slower than a snail on a salt track. "Emulation works but is slow. This is especially true for applications that interact heavily with the operating system or require significant processing power," João Reis points out. Windows 10 and 11 support emulation, but if you want speed, native ARM64 support is the way to go.
Below, we'll provide tips and tricks for porting your applications to ARM64, complete with real-world examples from our expert engineers at Janea. Let's dive in!
If your code includes low-level processor-specific instructions, get ready to roll up your sleeves. You'll need to rework this for ARM64. Applications built with higher-level or portable languages might dodge this bullet, but anything involving assembly or intrinsics will need attention. If you already have a mobile or Linux ARM version, you might be able to reuse parts of the existing code.
When we ported Node.js Mobile to run on ARM Apple devices, we had to introduce some assembly changes to handle the different calling conventions used by Apple ABI:
Handling Apple's calling convention:
https://github.com/JaneaSystems/nodejs-mobile/commit/91e5413390b5e07c066c24cb1464e75416838b31
This commit introduces changes to the arm64_CallFunction assembly code to support ChakraCore's custom ABI on iOS ARM64 devices. The main changes involve:
Further adjustments:
https://github.com/JaneaSystems/nodejs-mobile/commit/9e0b148670c990acf17bc503b430a6d78a2eeaa9
This commit makes further modifications to the arm64_CallFunction:
Final tweaks:
https://github.com/JaneaSystems/nodejs-mobile/commit/5d8380ea30a032a205a4afc4453911df967ae776
This commit makes additional refinements:
Compiler-specific extensions or intrinsics that have been in use for a long time may have better alternatives at the programming language level. For example, to enable V8 (formerly Node.js' JavaScript engine) to compile on ARM64 with Visual C++, we replaced some GCC-specific instructions that were only used on ARM with standard C++ code that can be compiled with any supporting compiler:
https://chromium.googlesource.com/v8/v8/+/0f81fe3a06e1ad726e4be4fb04a53befd40d860a%5E%21/
Another example of processor-level changes is our work on Microsoft PowerToys, where we reviewed an open source contribution that added support for ARM-specific registers in exception handling. This pull request adds support for ARM-specific registers in exception handling for PowerToys:
https://github.com/microsoft/PowerToys/pull/17587
These modifications demonstrate the detail required when porting low-level code to a new architecture, especially when dealing with platform-specific calling conventions and CPU features.
All of your dependencies need to play nice with ARM64. It's like trying to fit a square peg in a round hole if they don't. Here's how we've handled dependency issues in various projects:
In some cases, you may need to find ARM64-compatible alternatives for your existing dependencies. For example, PyTorch uses Intel's Math Kernel Library on Windows Intel platforms. As part of an ongoing collaboration to add Windows ARM64 support to PyTorch, we developed prototypes to evaluate the performance and functionality of the following libraries on ARM:
If you're shipping native dependencies, they need to handle ARM64 as well. In PowerToys, we had to ensure the native Visual C++ Redistributables were compatible with ARM64:
https://github.com/microsoft/PowerToys/commit/ba4b9cf549adee784e2e3a7b51aa7d2b54a55a87
Sometimes, you may encounter dependencies that don't have suitable installers for ARM64. In such cases, you might need to temporarily disable affected modules. We faced this issue with PowerToys:
https://github.com/microsoft/PowerToys/commit/f6a63582a26336b7ec285e1e9acb128ba494b715
In some cases, you might need to contribute to your dependencies to add ARM64 support. While adding ARM64 support to Node.js, we encountered a new dependency that didn't support ARM64 Windows. We raised the issue and helped fix it:
https://github.com/simdutf/simdutf/issues/216
https://github.com/nodejs/node/pull/46800
Remember, if a dependency is blocking your ARM64 build, you have several options:
By carefully managing your dependencies, you can ensure a smooth transition to ARM64 architecture and take full advantage of its benefits.
Windows on ARM64 isn't just Windows on Intel with a new coat of paint. There are subtle but crucial differences. For example, there are new environment variables and ARM-related values (e.g., for the PROCESSOR_ARCHITECTURE environment variable). Executables that run under x86/x64 emulation will see the system a bit differently. Depending on how much your application interacts with the system (environment variables, registry, COM objects, etc.), you might need to make some small but often confusing adjustments.
There are new environment variables and ARM-related values to consider. Executables under emulation will see the system differently. This can be a bit of a rabbit hole, but it's essential to get it right.
One notable issue we faced in PowerToys was the user PATH not being available for child processes. We had to implement workarounds to handle this:
https://github.com/microsoft/PowerToys/commit/fb7a85ec81f2766562b42bd91fc80df94e4738e7
This commit implements a workaround for the user PATH not being available for child processes on ARM64 Windows:
https://github.com/microsoft/PowerToys/commit/c601a3e3e22d919ddb913faef1bc80b02999fe8d
This commit provides further adjustments to the PATH fix for child processes:
Your build system needs to support ARM64, and you need to configure it to produce ARM64 binaries. Here's a guide on how to add an Arm64 configuration to your project in Visual Studio: https://learn.microsoft.com/en-us/windows/arm/add-arm-support#step-1---add-an-arm64-configuration-to-your-project-in-visual-studio
Take a look at how we implemented changes for the build systems on PowerToys, Node.js, and node-gyp:
Packaging for ARM64 isn't just about ticking a checkbox in your installer settings. If your application is packaged in an installer, you must ensure it supports ARM64. If you're using MSI installers, you will need to build a new one and make sure any Custom Actions are working correctly. For example, the WiX Toolset included support for ARM64 since version 3.14.
For PowerToys, we added ARM64 binaries to releases, ensuring users had access to ARM64-optimized versions: https://github.com/microsoft/PowerToys/commit/13750188fd521bb695ebd729b54d492995c05f2b.
We also supported ARM64 MSI packages in Node.js, adding WiX Toolset 4 support and official ARM64 releases:
MSI Package: https://github.com/nodejs/node/commit/0b66df61efec996cef593fbdfa18751c55d19153
Official ARM64 Releases:
https://github.com/nodejs/build/commit/76fd047c18bf3b5e460b92d1b2daeb696eb886a7
https://github.com/nodejs/build/commit/f0640bbbd6f1cd6f51c7915d098dcd3f344bdaf6
https://github.com/nodejs/nodejs.org/commit/b40c70cd04f18d27a9634536b0f518fa5305fa5c
Testing on ARM64 requires more than just running your existing tests. Your CI system needs to be set up to handle the ARM64 binary independently to ensure it remains functional. If you're using a CI system on the cloud, Azure has ARM64 Windows VMs available.
In our continued work maintaining Microsoft Powertoys, we perform manual tests on every release and check utilities to make sure they are still working on Windows 10 and Windows 11.
Test the program on a clean system without any other software installed to ensure that all dependencies are present.
The CI infrastructure needs to be set up to enable testing on Windows ARM64. Here's an example of how we did this for the Node.js CI:
https://github.com/nodejs/build/pull/3211/files
This pull request sets up ARM64 testing infrastructure for Node.js CI and:
After fixing all the tests that had issues on ARM64, we enabled testing by default on the Node.js CI.
https://github.com/nodejs/build/commit/af948e4eb12424dcab5e9738a10077b167d51034
This commit enables ARM64 testing by default on the Node.js CI:
We reviewed an Open-Source community contribution to PowerToys aiming to ensure new contributions cover ARM64: https://github.com/microsoft/PowerToys/pull/19878
Cross-compilation can be a lifesaver if you don't have an ARM64 machine on hand. But you should always test on native hardware to catch any platform-specific quirks.
In PowerToys, we added native compilation support based on existing cross-compilation support: https://github.com/microsoft/PowerToys/commit/57bde1c54c36f8387741557877e2e20161f0e1e8.
ARM64EC (Emulation Compatible) is a powerful tool in the ARM64 porting process, designed by Microsoft to facilitate a smoother transition for complex applications. Key features of ARM64EC include:
ARM64EC is particularly useful for large, complex applications with numerous dependencies. It allows teams to start benefiting from ARM64 performance improvements while gradually transitioning their codebase and dependencies. For more detailed information, refer to the official Microsoft documentation on ARM64EC.
As ARM64 for architecture becomes more prevalent, porting to ARM64 is a complex but necessary process to ensure your applications run at their best. We hope this guide has helped you in whatever stage of transitioning your software to ARM64 architecture. We're ready to help you navigate this transition if you need further assistance, ensuring your software runs smoothly and efficiently on ARM64 platforms. Whether you're a technical team embarking on a porting project or a C-suite executive seeking reliable expertise, we're here to support you every step of the way. Speak with our expert team here.
Ready to discuss your software engineering needs with our team of experts?