在苹果向 Apple Silicon 过渡期间,应用必须在 Arm 原生编译与通过 Rosetta 模拟 x86_64 之间二选一,无法在同一进程中兼顾。Windows on Arm 虽也采用类似模拟机制,但微软通过 Arm64EC 技术更进一步,实现了单个 Windows 进程同时运行原生 Arm 代码和模拟 x64 代码。

Arm64EC:模拟兼容的中间地带

Arm64EC(Emulation Compatible)是微软于 2021 年随 Windows 11 on Arm 推出的应用程序二进制接口(ABI)。它使得在 Arm 版 Office 中无缝运行旧版 x64 插件成为可能。当 x64 应用调用 Windows 系统组件时,系统可动态调整原生运行比例。在惠普 OmniBook Ultra 16 等设备上,Nvidia RTX Spark 驱动已将大部分用户态组件分别打包为纯 Arm64 和 Arm64EC 版本。

Arm64EC 本质上是遵循 x64 规则的 Arm 代码。其指令为真正的 Arm64 指令,在 CPU 上原生执行,但在数据结构、栈布局等方面严格遵循 x64 软件约定。因此,Arm64EC 编译的代码在架构检查中会自称 x64(定义 _M_AMD64 宏),任务管理器将其显示为“ARM64 (x64 compatible)”。这一设计主要旨在解决插件兼容难题:开发者可将主程序重新编译为原生 Arm,同时在同一进程中通过 Prism 模拟层加载未移植的 x64 第三方 DLL,无需等待上游开发者适配。

技术权衡:寄存器限制与调用开销

为实现兼容,Arm64EC 做出了一定牺牲。由于 x64 的 CONTEXT 结构限制了线程状态保存,Arm64EC 仅能使用部分 Arm 寄存器(如 X0-X12 等映射到 x64 寄存器),而 X13、X14 等寄存器及 V16-V31 向量寄存器被禁止使用,因其值无法在上下文切换或异常间保留。此外,可变参数函数(如 printf)需遵循类似 x64 的参数传递规则,进一步限制了可用寄存器。

另一大开销来自架构间的跳转。由于编译器在编译时往往无法确定函数指针指向的是原生 Arm 还是模拟 x64 代码,微软引入了“call checker”和“thunk”机制。每次间接调用均需经过检查:若目标为 Arm64EC 则原生执行,若为 x64 则通过 thunk 路由至模拟器。尽管 Arm64EC 函数间的直接调用无额外开销,但跨架构调用及间接调用均存在性能损耗。为此,微软还推出了 Arm64X 文件格式,将 Arm64 和 Arm64EC 代码打包于同一文件,类似红蓝 3D 图像,使不同架构应用能加载各自的代码段,简化了系统 DLL 的分发。

对比 macOS:生态策略决定技术路径

苹果对 Apple Silicon 的处理方式截然不同。macOS 严禁在同一进程中混合 arm64 和 x86_64 代码,Rosetta 转换应用于整个进程。若原生 Arm 应用尝试加载 Intel 插件,系统将报错。苹果建议通过 XPC 在独立进程中运行异构插件,如 Audio Units 利用 AUHostingCompatibilityService 在外部运行 Intel 插件。然而,VST2/VST3 等插件仍迫使宿主程序(如 Ableton)整体通过 Rosetta 运行。

这种差异源于生态策略:苹果明确 C++ ABI 将与 iOS 设备对齐而非 Intel Mac,且 Rosetta 仅支持至 macOS 27,意在彻底淘汰 Intel 代码。相比之下,Windows 需长期兼容海量旧软件,Arm64EC 正是在此背景下诞生的务实方案。

开源生态跟进:Wine 实现支持

Arm64EC 是微软特有的非标准 ABI,Arm 官方规范并未收录。尽管 GCC 尚未支持,但 LLVM 自 18.1 版本起已提供支持,Rust 也将其列为 Tier 2 目标。值得注意的是,Wine 10.0 于 2025 年 1 月发布时正式加入了对 Arm64EC 的支持,允许 Wine 代码以原生方式运行,仅模拟应用的 x86-64 部分。FEX 模拟器在构建为 Arm64EC 时也实现了相应接口,使得 Linux 上的 Arm 应用能借助类似技术运行 x64 代码,但需注意 Wine 10.0 要求 4K 内存页面大小以符合 Windows ABI 规范。