August 9, 2022·4 min read·engineeringcpparm64ecmsvcabi

Visual Studio 17.3 makes Arm64EC production supported

Arm64EC lets Windows C++ applications move selected components to native Arm execution while retaining x64 dependencies. Visual Studio 17.3 makes that toolset production supported, with explicit ABI constraints.

A C++ application can have portable source and still depend on a binary that its developers cannot rebuild. That dependency becomes a practical obstacle when moving to another processor architecture. Microsoft's August 9 announcement of production support for Arm64EC addresses applications blocked by x64 libraries and plug-ins. Visual Studio 2022 17.3 takes the toolset out of experimental status after a year of stabilization. It supports building Arm64EC applications for Windows 11 on Arm.

The useful change is the ability to ship a partial port. An application can retain an x64 dependency while moving code it controls to native Arm execution. That makes ownership of dependencies part of the migration plan. A vendor does not have to coordinate every component's architecture change before delivering any native code.

I value C++ most when control over performance survives contact with an existing codebase. Its accumulated complexity makes that control expensive. Arm64EC addresses a specific part of that expense, the need to preserve binary compatibility while changing how selected code executes.

Compatibility at the binary interface

Microsoft's ABI documentation, dated June 3, explains the mechanism. Arm64EC follows x64 software conventions for interoperability, including data alignment and stack usage. Windows emulates the x64 code, while Arm64EC code runs natively. The ABI restricts the available Arm register set so that register state can map to the emulated x64 context.

Calls crossing between the two kinds of code use entry and exit thunks. These are generated adaptation routines that handle differences in arguments, return addresses and register preservation. An entry thunk allows an x64 caller to enter an Arm64EC function. An exit thunk lets Arm64EC code call an x64 function through the emulator. The compiler and operating system cooperate at that boundary.

This explains why compiling for Arm64EC is a distinct target choice. Both sides must agree on the binary interface even though they execute different instruction sets. The ABI documentation also states that Arm64EC does not resolve differences between the x64 and Arm memory models. Binary interoperability therefore has a narrower meaning than complete equivalence of processor behavior.

A migration can follow project boundaries

The original Visual Studio toolset announcement, published in August 2021, describes incremental adoption through MSBuild and CMake. In Visual Studio's Configuration Manager, developers can add an ARM64EC solution platform and copy settings from x64. Individual projects inside that solution can remain configured for x64. The solution platform does not require every project to produce Arm64EC code.

For the Ninja configuration described in that announcement, these properties belong inside the relevant CMake configure preset. This is a fragment, not a complete preset file.

{
  "architecture": {
    "value": "arm64ec",
    "strategy": "external"
  },
  "environment": {
    "CFLAGS": "/arm64EC",
    "CXXFLAGS": "/arm64EC"
  }
}

The architecture selection and compiler flags express the target explicitly. The same source describes using the Visual Studio generator with the architecture strategy set to set. These generator-specific instructions matter because a configuration copied without its build-system context can describe a different compilation environment.

I prefer this kind of incremental change for a language whose codebases often contain several generations of design. It gives the application owner a migration unit that can match the components they maintain. An unavailable dependency remains a constraint, but it need not determine the execution mode of every other component.

Plug-in hosts demonstrate the practical effect

Pete Brown's June demonstration of an Arm64EC audio plug-in host makes the compatibility requirement concrete. He compiled Steinberg's VST3 SDK editor host for Arm64EC and loaded an unchanged x64 Valhalla VintageVerb DLL on a Surface Pro X. The host's architecture changed without requiring a rebuilt plug-in.

The demonstration also distinguishes Arm64EC from conventional Arm64 builds. A plug-in rebuilt for native execution in that host needs the Arm64EC target. The documented compatibility combinations pair an Arm64EC host with Arm64EC or x64 plug-ins. A conventional Arm64 host uses conventional Arm64 plug-ins. Arm64EC does not enable loading 32-bit x86 plug-ins into this process.

That distinction affects the product being distributed. Calling both builds "Arm" would conceal an interface requirement that determines whether a plug-in can load. For a host with third-party extensions, retaining the x64 plug-in path can preserve functionality while its own code moves to Arm64EC. The June example anticipated production readiness in 17.3, which today's announcement now supplies.

Supported does not mean every x64 construct carries over

The 17.3 compiler backend announcement records explicit errors for unsupported Arm64EC uses of __vectorcall, __m256 and __m512. Microsoft lists full __vectorcall support as future work. These diagnostics make the current toolset's limits visible during compilation.

Those restrictions deserve attention in performance-sensitive C++ code, where calling conventions and vector types can be deliberate interface choices. A dependency that remains x64 and source being rebuilt for Arm64EC pose different questions. Production support establishes a supported toolchain for the latter, with the documented restrictions still applying. The application can use mixed execution to retain an existing binary while adapting the code selected for native compilation.