ISA 越不同,难度就越大。 而且开销越大,尤其是后端。这并不像将不同的前端添加到常见的后端微架构设计上那么容易。
如果它只是不同解码器的裸片面积成本,而不是其他功率或性能差异,那么如今这将是微不足道的,并且完全可行,因为晶体管预算很大。 (在芯片的关键部分占用空间,将重要的东西放在更远的地方仍然是一个成本,但这不太可能成为前端的问题)。时钟甚至电源门控可以完全关闭任何未使用的解码器。但正如我所说,它没有那么简单,因为后端必须设计为支持 ISA 的指令和其他规则/功能; CPU 不会解码为完全通用/中性的 RISC 后端。相关:Why does Intel hide internal RISC core in their processors? 对现代英特尔设计中类似 RISC 的内部微指令有一些想法和信息。
例如,向 Skylake 添加 ARM 支持功能会使其在运行纯 x86 代码时变得更慢且能效更低,并且会占用更多的芯片面积。这在商业上不值得,因为它的市场有限,并且需要特殊的操作系统或管理程序软件才能利用它。 (尽管这可能会随着 AArch64 变得更加相关而开始改变,这要感谢 Apple。)
一个可以同时运行 ARM 和 x86 代码的 CPU 在其中任何一个上都比只处理一个的纯设计要差得多。
-
高效运行 32 位 ARM 需要支持完全预测执行,包括加载/存储的故障抑制。 (与 AArch64 或 x86 不同,它们仅具有 ALU 选择类型指令,例如 csinc 与 cmov / setcc,它们仅对 FLAGS 及其其他输入具有正常的数据依赖性。)
-
ARM 和 AArch64(尤其是 SIMD shuffle)有几条产生 2 个输出的指令,而几乎所有 x86 指令只写一个输出寄存器。因此,x86 微架构旨在跟踪最多读取 3 个输入(在 Haswell/Broadwell 之前为 2 个)并仅写入 1 个输出(或 1 个 reg + EFLAGS)的微指令。
-
x86 需要跟踪 CISC 指令的单独组件,例如内存源操作数的加载和 ALU 微指令,或内存目标的加载、ALU 和存储。
-
x86 需要一致的指令高速缓存,并侦听修改已获取并在流水线中运行的指令的存储,或者以某种方式处理至少 x86 强大的自修改代码 ISA 保证(@ 987654322@).
-
x86 需要strongly-ordered memory model。 (程序顺序+带有存储转发的存储缓冲区)。您必须将其烘焙到您的加载和存储缓冲区中,所以我希望即使在运行 ARM 代码时,这样的 CPU 基本上仍会使用 x86 更强大的内存模型。 (现代 Intel CPU 推测性地提前加载并在错误推测时执行内存订单机器清除,所以也许您可以让这种情况发生并且简单地不执行那些管道核弹。除非是由于错误- 预测负载是否正在通过此线程重新加载最近的存储;当然仍然必须正确处理。)
纯 ARM 可以有更简单的加载/存储缓冲区,它们之间的交互不那么频繁。 (除了使stlr / ldapr / ldar release / acquire / acquire-seq-cst 更便宜,而不仅仅是完全停止。)
-
不同的页表格式。 (您可能会选择一个或另一个供操作系统使用,并且只支持本机内核下的用户空间的另一个 ISA。)
-
如果您确实尝试完全处理来自两个 ISA 的特权/内核内容,例如因此,您可以使用任一 ISA 的 VM 进行硬件虚拟化,您还可以使用控制寄存器和调试工具等东西。
更新:Apple M1确实支持强大的 x86 风格的 TSO 内存模型,allowing efficient+correct 将 x86-64 机器码二进制翻译成 AArch64 机器码,无需每次加载和存储都使用ldapr / stlr。它还有一个运行原生 AArch64 代码的弱模式,toggleable by the kernel。
在 Apple 的 Rosetta 二进制翻译中,软件处理了我提到的所有其他问题; CPU 只是在执行本机 AArch64 机器代码。 (而且 Rosetta 只处理用户空间程序,因此甚至不需要模拟 x86 页表格式和类似的语义。)
这已经存在于 ISA 的其他组合中,特别是 AArch64 + ARM,而且 x86-64 和 32 位 x86 的机器代码格式略有不同,并且寄存器集更大。那些对 ISA 的设计当然是为了兼容,并且新 ISA 的内核支持将旧 ISA 作为用户空间进程运行。
在最简单的一端,我们有 x86-64 CPU,它支持在 64 位内核下运行 32 位 x86 机器代码(在“兼容模式”下)。它们对所有模式完全使用相同的管道获取/解码/发布/乱序执行管道。 64 位 x86 机器代码有意与 16 位和 32 位模式足够相似,因此可以使用相同的解码器,只有少数与模式相关的解码差异。 (就像 inc/dec vs. REX 前缀。)不幸的是,AMD 故意非常保守,为 64 位模式保留了许多小的 x86 缺陷,以保持解码器尽可能相似。 (也许如果 AMD64 甚至没有流行起来,他们不想被困在花费人们不会使用的额外晶体管上。)
AArch64 和 ARM 32 位是独立的机器代码格式,在编码方面存在显着差异。例如立即操作数的编码方式不同,我假设大多数操作码都是不同的。据推测,管道有 2 个独立的解码器块,前端根据模式通过一个或另一个路由指令流。与 x86 不同,两者都相对容易解码,所以这大概没问题;两个块都不必很大才能将指令转换为一致的内部格式。不过,支持 32 位 ARM 确实意味着以某种方式在整个管道中实现对预测的有效支持。
早期的 Itanium (IA-64) 还具有对 x86 的硬件支持,定义了 x86 寄存器状态如何映射到 IA-64 寄存器状态。这些 ISA完全不同。我的理解是,x86 支持或多或少是“固定”的,芯片的一个单独区域专门用于运行 x86 机器代码。性能很差,比好的软件仿真还差,所以一旦准备好,硬件设计就放弃了。 (https://en.wikipedia.org/wiki/IA-64#Architectural_changes)
这是否意味着 x86 指令在执行过程中被翻译成一些奇怪的内部 RISC ISA?
是的,但是“RISC ISA”与 ARM 不同。例如它具有 x86 的所有怪癖,例如,如果移位计数为 0,则不修改 FLAGS 的移位。(现代英特尔通过将shl eax, cl 解码为 3 uop 来处理这一点;如果后面的指令想要读取 FLAGS,Nehalem 和更早的版本会停止前端从一个班次。)
可能需要支持的后端怪癖的一个更好示例是 x86 部分寄存器,例如写入 AL 和 AH,然后读取 EAX。后端的 RAT(寄存器分配表)必须跟踪所有这些,并发出合并 uops 或它处理它的问题。 (见Why doesn't GCC use partial registers?)。