386架构中16位寄存器运算保留高位是否存在优势?
在现代x86汇编中,通常会避免使用8位和16位寄存器,因为这类寄存器的运算会保留高位的56/48位不变,这会因部分寄存器停顿导致性能问题——此时需要复制并合并这些高位到目标寄存器。amd64架构将寄存器扩展至64位时,规定32位运算会将寄存器的高32位清零,这一设计显然能避免32位运算的部分寄存器停顿问题。
已有不少讨论探讨32位运算的这一行为缘由,但我尚未找到关于8/16位寄存器该行为的答案甚至推测。
我对8位寄存器行为的理解
最初的8086旨在(在很大程度上)与8080源代码兼容,8080采用8位ALU但使用16位地址,地址由成对的8位寄存器构成。因此,8位寄存器的合并行为是实现8080到8086指令映射的必要条件。
SSE寄存器的类似行为及推测
除8/16位寄存器外,非VEX编码的SSE指令也存在类似行为:SSE引入了128位宽的向量寄存器,AVX将其扩展至256位,但规定修改低128位的指令应保留寄存器的高半部分,唯一例外是使用新VEX前缀的128位指令会将寄存器的高半部分清零。
我认为SSE寄存器的这一行为是出于ABI考量:可能需要调用AVX出现前编译的函数,这类函数可能需要保存/恢复向量寄存器的值,但由于仅使用SSE指令,只能保存低128位。因此,硬件在处理未感知AVX的指令时会自动保留高128位。另一种无需特殊硬件处理的解决方案是修改支持AVX程序的ABI,将AVX寄存器的高半部分设为调用者保存,但可能存在未采用该方案的原因。
关于16位寄存器的核心疑问
不过,我认为向后兼容性和ABI考量的论点不适用于16位寄存器,且难以找到其他合理解释。
据我所知,386是首款将寄存器扩展至32位的x86处理器。16位操作的操作码被重新用作默认32位操作,需使用操作数大小覆盖前缀才能再次使用16位寄存器。因此,该架构完全可以让16位操作清零寄存器的高半部分,而非采用实际的合并行为:
- 不存在ABI问题:任何16位保存/恢复指令都会自动保存完整的32位寄存器;
- 不存在向后兼容性问题:所有16位程序都不知道寄存器宽度超过16位,这与了解8位寄存器配对的8080程序不同。
基本上,我想不出为何要选择合并行为而非清零行为。
我明白16位寄存器保留高位的设计理由可能从未被解释,但我对此疑惑已久,特此提问,希望有人知晓或至少能给出合理猜测。
内容的提问来源于stack exchange,提问作者user19232978

