首页 > 解决方案 > 为什么 x86 通常不允许不是第一个源寄存器的目标寄存器?

问题描述

在 RISC-V 中,可以Regs[x1] <- Regs[x2]+Regs[x3]使用指令执行整数运算

add x1,x2,x3

在 x86 中,同样的操作显然需要两条指令,

mov x1,x2
add x1,x3

src1 <- src1 op src2模式似乎对于 x86 中的其他基本指令很常见,例如andorsub。但是,x86 确实有dest <- src1 op src2例如 for floating point adds。

是双指令模式mov x1,x2op x1,x3; 通常将宏融合到单个微操作中?或者对于这些操作来说,独立目的地是如此罕见,以至于 x86 架构不会在单个 uop 中允许它?如果是这样,不允许独立目的地提供什么效率?

标签: assemblyx86cpu-architectureriscv

解决方案


几乎重复x86 cpu有什么样的地址指令?这解释了机器代码的原因(以及一般情况的一些例外)。

如果是这样,不允许独立目的地提供什么效率?

只是代码大小。它使其他一切变得更糟,这就是为什么所有现代高性能设计都提供 3 操作数指令的原因,以及如果他们从头开始重新构建 x86-64 以提高性能,人们会怎么做。

x86 使用紧凑的可变长度指令编码,并从 8 位 8080 演变为 2 操作数 ISA,后者或多或少是 1 操作数 ISA,其中大多数操作码都隐含一个操作数(通常是累加器)。

您可以说,作为 CISC ISA,x86 将其额外的编码空间用于内存源操作数的可能性,而不是单独的目标。尽管这只是正确的,因为只有 2 位编码寄存器 vs. [register] 间接 vs. [reg+disp8] vs. [reg+disp32]。其余的空间不存在,因为典型的指令只有 2 个字节长,操作码 + modrm。(加上寻址模式的前缀、立即数和/或额外字节)。

有趣的事实是,16 位的长度与 ARM Thumb 的长度相同,后者做出了相同的选择,主要是 2 操作数编码,因为这就是您如何以有时需要更多指令为代价来保持较小的指令。在最初的 8086(尤其是带有半宽总线的 8088)上,代码获取是主要瓶颈,无论指令数量如何,节省代码字节通常都会提高性能。

x86 机器代码当时一成不变,我们仍然坚持使用它。这对于当今的 CPU 来说极其不方便,32 位模式下的 VEX 和 EVEX 编码被其他指令的无效编码所束缚;这完全是一团糟,而且解码起来非常慢+耗电。例如,英特尔 CPU 有一个单独的流水线阶段,只是为了在将指令长度/边界提供给解码器之前找到它们。这就是为什么现代 CPU 具有解码的 uop 缓存,以避免在“热”代码区域中重新解码,以及由于这些长管道而需要良好的分支预测的原因。

任何抛弃 2 操作数编码以腾出更多空间的小改动都会引发这样一个问题:为什么要保留任何遗留包袱,为什么不从头开始呢?然后,为什么是 x86-64,为什么不像 AArch64 那样干净整洁的设计呢?


另请注意,ADDPDandADDSD是 2 操作数 SSE 指令。同一指令的 3 操作数非破坏性目标编码是 AVX 的新功能,称为VADDPD/ VADDSD


MOV + ADD 的效率

mov/ add(和 shift)可以用 来完成lea,例如lea eax, [rdi + rsi*4]实现return x + y*4;,以便解决最常见指令的问题。 对不是地址/指针的值使用 LEA? 看看 x86-64 优化的编译器输出。

实践中的 x86 微架构不会宏融合 mov + op,尽管这在理论上是可能的。在实践中,编译器确实必须使用大量mov reg,reg指令,但每条 ALU 指令明显少于 1 条。硬件供应商在解码时还没有开始寻找融合机会。目前,他们只将 cmp/test + branch 融合到一个 uop 中。(或者在 Intel Sandybridge-family 上,还有其他 ALU+分支指令,如 AND+branch 或 DEC+branch。) 当代 x86 处理器中的指令融合是什么?还涵盖了内存源 CISC 指令中 load+ALU 微指令的微融合。

在问题/重命名时消除 MOV确实使 MOV+ALU 对对于关键路径仍然只有 1 个周期延迟。 (虽然您有时可以通过让关键路径使用原始路径来获得相同的延迟优势,并且一些较短延迟或独立的 dep 链使用副本。但通常这需要循环展开。)

但是,mov-elimination 对前端吞吐量或保持无序窗口更小没有帮助。对于管道的其余部分,MOV 成本与 NOP 相同。

Haswell 到 Skylake 的前端宽度与后端的 ALU 执行单元的数量相同。即使使用 Ice Lake 和 Zen(更宽的前端,仍然“仅”4 个整数 ALU 执行单元),未消除mov也很少会成为瓶颈。大多数代码包括偶尔的存储或非微融合加载uop。


推荐阅读