Multi-architecture COFF object files for ARM64X using an extra section

I would like to propose adding support for multi-architecture object files that contain both ARM64EC and AArch64 object files as the result of compilation with -marm64x. This is an alternative to [RFC] Multi-architecture COFF object files for ARM64X that does not need an archive format change from [RFC] Introduce a -wholearchive marker embedded in archives themselves.

Together with ARM64EC, Windows on ARM introduced ARM64X image files, which are binaries containing both ARM64EC and AArch64 code. In MSVC, this is implemented entirely as a linker feature. Users are expected to compile sources separately for both targets and then pass both sets of object files to the linker ( Build Arm64X Files | Microsoft Learn ). We already support this workflow in both Clang and LLD. The build process typically looks like this:

clang -target aarch64-windows -c foo.c -o foo-aarch64.o
clang -target arm64ec-windows -c foo.c -o foo-arm64ec.o
clang -marm64x -o bar.exe foo-aarch64.o foo-arm64ec.o

This differs significantly from a typical build process. The requirement to produce two object files for every source file is challenging for build systems such as CMake, autoconf or Meson. I believe this can be significantly simplified by moving more of the logic into the toolchain itself. Ideally, the above example could instead become:

clang -marm64x -c foo.c -o foo.o
clang -marm64x -o bar.exe foo.o

This is straightforward for build systems to understand and is effectively equivalent to CFLAGS=-marm64x LDFLAGS=-marm64x.

This requires foo.o to contain both AArch64 and ARM64EC object files. The concept is not entirely new; Mach-O has supported multi-architecture binaries for years. With Mach-O, users typically have two options: build separate images and merge them afterward or build each source file into a multi-architecture object file. The former approach is not feasible for ARM64X due to format constraints, so I would like to provide an experience similar to Mach-O multi-architecture object files.

I’d like to introduce a new .llvm.arm64x section, similar to how .llvm.lto is used for embedding bitcode object files. This allows aware tools to leverage the additional capability while ensuring unaware tools simply ignore the section.

On the Clang side, this is achieved by compiling separately for both the ARM64 and ARM64EC targets and merging the results via an additional llvm-objcopy job. lld-link then selects the correct view of the file depending on the target architecture. Additionally, the archive writer splits the object file into separate members so that they are properly reflected in the symbol map. An advantage of such treatment in archives is that the resulting static library can be used seamlessly by the MSVC linker, as it no longer relies on the .llvm.arm64x section.

PR with a draft implementation: [COFF][ARM64X] Multi-architecture COFF object files for ARM64X by cjacek · Pull Request #202740 · llvm/llvm-project · GitHub

Love the idea.

Another use-case to consider: compiling for the same arch but for different target CPUs or features. This would need linker/loader support like Arm64X to know which variant of each function (or which section) to load at runtime.

Thanks! Yes, on top of refactoring needed for this, we could extend LLD’s addObjectFile to pick such embedded objects.

Since there were no objections, I plan to move forward with the implementation. Following the discussions in the MRs, we have decided to use .obj.arm64ec for the section name (see #205156), instead of .llvm.arm64x from the original proposal. I will be opening PRs for the remaining parts shortly.

1 Like