...

Porting Software to ARM64 Architecture: Everything You Need to Know

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.

Understanding ARM64 Architecture

ARM32 vs. ARM64

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:

  • Performance: ARM64 provides significant performance improvements over ARM32.
  • Energy Efficiency: ARM64 processors are designed to be more power-efficient, which is crucial for extending battery life in mobile devices and reducing energy consumption in data centers.
  • Scalability: ARM64 architecture is highly scalable and suitable for various devices, from smartphones to servers.

Emulation Performance

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!

Your Guide to Porting Applications to ARM64

Processor Level Changes, Assembly Code, and Intrinsics

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:

  • Adding a new macro PROLOG_SAVE_REG_PAIR_INDEXED for iOS
  • Modifying the existing PROLOG_SAVE_REG_PAIR macro for non-iOS platforms
  • Adjusting the use of these macros in the arm64_CallFunction implementation to maintain semantic equivalence with the original Windows-based assembly file

Further adjustments:
https://github.com/JaneaSystems/nodejs-mobile/commit/9e0b148670c990acf17bc503b430a6d78a2eeaa9
This commit makes further modifications to the arm64_CallFunction:

  • Removes the use of PROLOG and EPILOG macros
  • Introduces explicit frame creation instructions
  • Adds CFI (Call Frame Information) directives
  • Adjusts register usage and stack operations

Final tweaks:
https://github.com/JaneaSystems/nodejs-mobile/commit/5d8380ea30a032a205a4afc4453911df967ae776
This commit makes additional refinements:

  • Further adjusts register usage
  • Modifies stack operations
  • Tweaks the overall implementation of arm64_CallFunction

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:

  • Adding ARM64-specific register definitions (X0 to X28, FP, LR, SP)
  • Implementing GetImageArchitecture function to detect ARM64 architecture
  • Modifying DumpExceptionInfo to handle ARM64-specific context information

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.

Dependency Management

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:

Evaluating Alternative Libraries

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:

Updating Native Dependencies

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:

  • We modified the installer to detect ARM64 architecture and install appropriate redistributables
  • We updated dependency management to include ARM64-specific versions of Visual C++ libraries

https://github.com/microsoft/PowerToys/commit/ba4b9cf549adee784e2e3a7b51aa7d2b54a55a87

Handling Unsupported Dependencies

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:

  • We implemented conditional compilation to exclude the affected module on ARM64 builds
  • We added runtime checks to disable the module's functionality on ARM64 systems

https://github.com/microsoft/PowerToys/commit/f6a63582a26336b7ec285e1e9acb128ba494b715

Contributing to Open Source Projects

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

Top Tips for Dependency Management when Porting to ARM64

Remember, if a dependency is blocking your ARM64 build, you have several options:

  • For internal dependencies, rebuild them with ARM64 support
  • For third-party dependencies, request the maintainer to add ARM64 support.
  • For open-source dependencies, check if a newer version with ARM64 support exists, or consider contributing the support yourself
  • As a last resort, consider removing or replacing the dependency

By carefully managing your dependencies, you can ensure a smooth transition to ARM64 architecture and take full advantage of its benefits.

Operative System Interaction

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.

Handling OS Differences

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:

  • Adding a new function GetUserPath to retrieve the user's PATH environment variable
  • Modifying the CreateProcessHelper function to include the user's PATH in the environment block

https://github.com/microsoft/PowerToys/commit/c601a3e3e22d919ddb913faef1bc80b02999fe8d
This commit provides further adjustments to the PATH fix for child processes:

  • Refactoring the GetUserPath function to use ExpandEnvironmentStringsW
  • Improving error handling and logging for PATH-related operations

Build System Adjustments

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:

Installer and Distribution Formats for ARM64

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

  • Updating the WiX Toolset configuration to support ARM64 builds
  • Modifying the MSI package generation process to include ARM64-specific

Official ARM64 Releases:

https://github.com/nodejs/build/commit/76fd047c18bf3b5e460b92d1b2daeb696eb886a7

  • We configured the build system to generate ARM64 binaries
  • We updated release scripts to include ARM64 packages in the official releases

https://github.com/nodejs/build/commit/f0640bbbd6f1cd6f51c7915d098dcd3f344bdaf6

  • We added ARM64-specific build configurations to the CI system
  • We modified release automation to handle ARM64 artifacts

https://github.com/nodejs/nodejs.org/commit/b40c70cd04f18d27a9634536b0f518fa5305fa5c

  • We added ARM64 download options to the website's download page
  • We updated release information to include ARM64-specific details

Testing and CI Adjustments

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.

Ensuring ARM64 Compatibility in CI

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:

  • Adds ARM64 Windows machines to the CI pool
  • Configures Jenkins to run tests on ARM64 hardware
  • Updates test scripts to support ARM64-specific scenarios

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:

  • It modifies the CI configuration to include ARM64 in the default test matrix
  • It updates test result reporting to handle ARM64-specific outcomes

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 vs. Native Compilation

Benefits and Challenges

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: A Tool for Gradual Porting

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:

  • Gradual Porting allows developers to port their applications to ARM64 incrementally rather than requiring a complete rewrite all at once.
  • Compatibility with x64 Code: ARM64EC enables ARM64 apps to interoperate with x64 plugins or dependencies that haven't been ported yet.
  • Performance Benefits: While not as fast as native ARM64 code, ARM64EC offers better performance than full x64 emulation.
  • Simplified Debugging: It allows debugging of both ARM64EC and x64 code together, streamlining the development process.

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.

Conclusion

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.

Related Blogs

Let's talk about your project

113 Cherry Street #11630

Seattle, WA 98104

Janea Systems © 2024

  • Memurai

  • Privacy Policy

  • Cookies

Let's talk about your project

Ready to discuss your software engineering needs with our team of experts?

113 Cherry Street #11630

Seattle, WA 98104

Janea Systems © 2024

  • Memurai

  • Privacy Policy

  • Cookies

Show Cookie Preferences