Windows 10 on ARM has always been able to run 64-bit apps

The transition from x86-based processors to ARM-based processors is one of the most interesting and exciting technology events for me right now, and it is happening so stealthily that most people don't even understand what is going on. I keep seeing reviews that say Windows 10 on ARM "can't run 64-bit apps" or articles titled "Will Windows 10 on ARM be affected by ARM dropping 32-bit support?" or other such misleading things. The confusion comes from a misunderstanding of what 32-bit and 64-bit even are - people don't realize that 64-bit ARM code exists at the same time as 64-bit x86 code.

Here's a handy table to hopefully clear things up before I delve into details:

AbilityWindows 10 on x86Windows 10 on ARMWindows RTMacOS on x86MacOS on ARMLinux
Run 32-bit x86Yes
Natively
Yes
Emulated
No
Never
No
Not Anymore
No
Never
Yes
Run 64-bit x86Yes
Natively
Yes (insider builds)
Emulated
No
Never
YesYes
Translated (Rosetta 2)
Yes
Run 32-bit ARMNoYes
Natively
Yes
Natively
No
Never
No
Never
Yes
Run 64-bit ARMNoYes
Natively
No
Never
NoYes
Natively
Yes
Run 32-bit RISC-VNoNoNo
Never
NoNoYes
Run 64-bit RISC-VNoNoNo
Never
NoNoYes

It's a bit much to take in, as usual due to historical considerations. For starters, the processors themselves: x86 processors include those made by Intel and AMD - there hasn't been a third major player for x86 in a long time, primarily due to Intel's licensing restrictions for the 32-bit x86 instruction set and AMD's licensing restrictions for the 64-bit x86 instruction set. x86 is what is known as a Complex Instruction Set Architecture, or CISC (with the second C being for Computer). They can do a wide range of complex instructions quickly, but they use a ton of electricity in the process, and generate a lot of heat as a result. On the other hand, ARM processors include those made by Qualcomm and Apple, as well as others - ARM licensing is more open than x86. ARM is a Reduced Instruction Set Architecture (RISC) meaning that they only process simple consistent instructions. They need more instructions in some cases to do the same things as fewer instructions in x86, but they also use much less electricity and generate much less heat. You may also have noticed RISC-V in the table, which is also a RISC processor architecture like ARM, but it is even more power efficient than ARM and more open as well, though also much newer and with much less adoption as a result. ARM has long dominated in the smartphone industry due to the better power efficiency and reduced heat output, and while we used to have MIPS as another RISC processor architecture, the MIPS company has decided to invest all its resources into RISC-V. ARM is also taking huge swaths of the datacenter industry under its belt, as data centers seek to use less electricity and generate less heat. If RISC-V proves to be more efficient, it will likely catch up to and even surpass ARM, but that's a ways off still.

Nearly all of the processors can have 32-bit and 64-bit variants, and for backwards compatibility, they can natively run code from half their native size - e.g. 32-bit x86 processors could run 16-bit code and 64-bit processors could run 32-bit code, natively. This backward compatibility is optional though and doesn't necessarily have to be supported by the processors. Apple, for instance, decided to stop supporting 32-bit x86 apps entirely, even if the processor itself might still have remnants of that support. ARM initially only had 32-bit processors, but now has moved to 64-bit and still supports 32-bit code as well. Apple's new M1 processors are ARM processors dubbed as Apple Silicon, and they likely don't have any 32-bit support at all even vestigially.

Windows RT was Microsoft's first foray into the world of ARM processors for desktop operating systems, and it flopped hard, mainly because it couldn't run x86 apps (which were the only Windows apps in existence at the time) and because even when developers did take the time to recompile their code for ARM, they were only allowed to do so as Universal Windows Platform (UWP) apps, which are more complicated to develop for and more limited than traditional Win32 apps. (Win32 is just the name for the kind of apps that get full unrestricted system access due to not being UWP apps, the 32 doesn't mean anything anymore as there can be 64-bit Win32 apps.) Making matters worse, apps for WIndows RT also apparently had to be hand-picked by Microsoft, so you couldn't just throw a 32-bit ARM version of your UWP app on the Microsoft store and expect to be able to download it on a Windows RT device. This completely crippled the platform, and now Microsoft no longer supports it.

Windows 10 on ARM, on the other hand, is the full entire Windows 10 operating system running on a 64-bit ARM processor. In theory Microsoft could have made a 32-bit ARM version of Windows 10 just like they have a 32-bit x86 version, and Windows RT was itself 32-bit, but there's really no benefit to doing this, so Windows 10 on ARM is entirely 64-bit native. However, there are still some benefits to 32-bit ARM software even on 64-bit ARM hardware, so Microsoft did decide to support both 32-bit and 64-bit ARM apps for Windows 10 on ARM. This also allowed supporting old 32-bit UWP apps that were made for Windows RT, in theory, as well as supporting traditional unrestricted Win32 apps for both 32-bit and 64-bit ARM. For supporting x86 apps though, they went with emulation: within the OS they run a software emulator that pretends to be an x86 processor. This emulator is itself running natively as ARM code, and it reads the x86 code and then executes corresponding ARM instructions. There is some overhead in this process incurred by the emulator having to interpret and understand the x86 code as it executes. For a long time now, only 32-bit x86 apps could be emulated, Microsoft is still building emulation support for 64-bit x86 apps. You can try it in Windows 10 insider builds, it'll likely be released fully either later this year or early next year.

Apple on the other hand decided not to use emulation, and instead went with translation: before the app even runs at all, Apple goes through all the x86 code of the app and generates corresponding ARM instructions, and then once it has done that, it finally runs this newly-minted ARM version of the app. There is also some trickery with their processors supporting some special hybrid ARM+x86 instructions to allow the Rosetta 2 translator to generate more optimal code. (Rosetta 1 was used back when Apple moved from PowerPC to x86.) As a result, x86 apps on MacOS run much faster than they would through emulation. In theory, Microsoft could do this too, it's just a matter of time.

 (Update 2023-11-22: the distinction between emulation and translation seems misleading, and actual tests show it's not as clear cut.)

So, now we get to the confusion. Microsoft, in their infinite wisdom, decided that 32-bit x86 should be denoted as just x86, and 64-bit x86 should be denoted as... x64. Yes, the lower number is the newer and (usually) better version. When you see people making the claim that Windows 10 on ARM can't run 64-bit apps, what they mean is that it can't yet run 64-bit x86 apps, and they have no understanding of the concept of 64-bit ARM apps. That x64 name doesn't help avoid confusion either. In their defense, a majority of apps are still only available as 64-bit x86 builds, which is why it's such a contentious issue. But don't be misled, Windows 10 on ARM has always been able to run 64-bit ARM apps. Additionally, when you hear people talking about ARM dropping 32-bit support, that only applies to new processors, it can not and will not affect existing devices. I also don't expect it to affect the ability to run 32-bit x86 apps - those can still run through emulation via 64-bit ARM code that emulates the x86 instructions, as discussed before. It is effectively a non-issue.

A screenshot of the Windows 10 on ARM system properties with a red rectangle highlighting the text 64-bit operating system, ARM-based processor

There are some cases where it is actually better to use 32-bit apps on 64-bit processors, but they are few and far between, and it primarily comes down to RAM usage. 32-bit code uses 32-bits for storing memory addresses, which is something code does all the time, whereas 64-bit code uses 64-bit addresses. If some environment doesn't have more than 3 or 4 GB of RAM, a 32-bit process could use less memory than its 64-bit equivalent. However, with the ever increasing amount of RAM we are shoving into our devices, this is quickly fading as a relevant argument for 32-bit apps. Nearly all software that has been ported to ARM is only distributed as 64-bit ARM. Apple stopped supporting 32-bit apps of any kind, the Google Play Store also stopped allowing submissions of 32-bit code, and now ARM itself is removing 32-bit code support from its future processor designs. 32-bit ARM is going by the wayside as it has little reason to exist. You can still build 32-bit ARM apps for Windows 10 on ARM today, but it's only useful for that small demographic of 4 GB RAM or less, and the differences are in practice negligible.

So how hard is it anyway to build ARM versions of apps that only have x86 versions? Well, if you have the source code, it is usually trivially easy to do. The only issues that tend to crop up are if you had hand-written some assembly code (which is the part that actually differs between different processor architectures) or if you had used special compiler intrinsics that rely on specific processor instructions. Those are usually rare and just need some replacing for ARM. In all other cases though, building an app for ARM is the same as building it for x86, you just select a different architecture while building and the compiler does all the work for you. With some languages, you don't even need to recompile the code, only the interpreter needs to be recompiled (e.g. Python, Java, C#). While building for ARM is trivially easy in most cases, optimizing for ARM is a new learning experience for many developers who use low level languages like C++, Rust, or C. You have to worry more about memory alignment and data access synchronization in ARM than you do in x86. The compiler can in most cases fix issues in these areas for you, but it will do so by generating slower code, so while an initial port will likely work without issue, it will likely also have some code paths that are unnecessarily slow. Those are the ones that take the most time to fix up. In the end though, you can easily match or surpass x86 in some way with ARM, if not in raw performance then at least in power efficiency. The crazy thing about RISC is that even if it takes longer to perform some task, it can still spend less electricity and generate less heat than when the CISC code does it faster. It is very much worth it to try and optimize code for ARM anyway, as some of the optimizations that benefit ARM can also benefit x86.

Did you know the Nintendo Switch uses an ARM processor? It's true - unlike the PS4/PS5 and the Xbox One and Xbox Series consoles which use x86-based processors to run 64-bit x86 code, the Nintendo Switch runs both 32-bit and 64-bit ARM code. If you are a game developer and you optimize your game for the Nintendo Switch and for Windows, congratulations, you already have optimal code for Windows 10 on ARM! Currently there are very few games that run on Windows 10 on ARM, and even fewer that run natively as ARM code rather than through emulation. I personally helped increase the list of native ARM games for Windows 10 on ARM when I ported the Five Nights at Freddy's series to UWP. They aren't particularly demanding games by any stretch, sure, but they are games and they now have native ARM versions available on the Microsoft Store, and I think that's really cool. (And for reference, you don't need to use UWP or the Microsoft Store to distribute native ARM apps for Windows 10 on ARM, you can do it the same way as on x86 via the Win32 app model).

One major caveat with Windows 10 on ARM though: Only DirectX is supported as the graphics rendering API, and only recent versions. Microsoft recently released an OpenGL compatibility pack, but this just emulates OpenGL under DirectX. Vulkan isn't supported yet. In theory OpenGL and Vulkan could be natively supported alongside DirectX, but this requires graphics driver support, which means collaboration between multiple companies and money being spent. It's early days still, so it is likely a difficult sell. OpenGL and Vulkan run fine on ARM Android devices though, so it's only a matter of time.

Bonus nonsense: In 64-bit Windows on x86, you have C:\Program Files (x86)\ and C:\Program Files\ with the latter being for 64-bit x86 apps and the former being for 32-bit x86 apps. In Windows 10 on ARM, you have those two directories and also a C:\Program Files (arm)\ for 32-bit ARM apps. The 64-bit ARM apps go in C:\Program Files\ - and, guess where the 64-bit x86 apps will go? You know Microsoft wouldn't pick something that makes sense like C:\Program Files (x64)\ - no, instead the 64-bit x86 apps will share a home with the 64-bit ARM apps in one big unhappy family under C:\Program Files\ because Microsoft said so. This complicates side-by-side installations for people who want to test the 64-bit x86 emulation speed compared to the native 64-bit ARM speed.

Microsoft is also doing something rather interesting to support 64-bit x86 apps in Windows 10 on ARM, and that is to introduce whole new architectures for the compiler to target so as to create hybrid x86+ARM binaries. It is currently unclear how exactly they work, but if you search online for ARM64EC and ARM64X you will find some information. In general it looks like whatever approach Microsoft used for supporting 32-bit x86 apps on Windows 10 on ARM is similar to the 32-bit ARM support, or the 32-bit x86 support on 64-bit Windows 10 on x86, whereas supporting 64-bit x86 apps on Windows 10 on ARM is forcing them to go down a whole entirely different route. It will be interesting to see what the final product ends up like.

A screenshot of the target machine options in the C++ linker settings in Visual Studio 2019

If like me you're curious about the MIPS options there, it's because Windows used to support multiple different processor achitectures before ARM. Microsoft and Apple have both jumped between different processor architectures in the past, and the fact they're doing it again now shows that we have cool times ahead.

Comments