1

我正在阅读英特尔文档,第一卷。1 并且有3.6.1 64-Bit Mode 中的操作数大小和地址大小一章。有三个前缀REX.W,操作数大小66和地址大小67前缀。并且提到操作数默认为 32 位大小。并且只能使用REX.W指令前缀(在其他前缀之后)更改它以使其长度为 64 位。

我不知道为什么,为什么我不能将完整的 64 位空间用于int操作数?跟标志有关系吗?或者为什么会有这个限制?(所以,C是否在 int 上unsigned int使用REX.W前缀和操作(正如也提到的,前缀仅持续用于特定指令,但不适用于整个段,这应该是(大小,地址或操作数的)默认值和包含在段描述符中)。

我理解正确吗?

4

1 回答 1

5

TL:DR:您有两个单独的问题。1 关于 C 类型大小,另一个关于 x86-64 机器代码如何编码 32 与 64 位操作数大小。编码选择是相当随意的,可能会有所不同。但是int是 32 位,因为这是编译器开发人员选择的,与机器代码无关。


int是 32 位的,因为这仍然是一个有用的大小。它使用一半的内存带宽/缓存占用int64_t。大多数 64 位 ISA 的 C 实现都有 32-bit int,包括 x86-64 的主流 ABI(x86-64 System V 和 Windows)。在 Windows 上,evenlong是 32 位类型,大概是为了与为 32 位编写的代码兼容,这些代码对类型大小做出了假设。

此外,当时 AMD 的整数乘法器在 32 位上比 64 位要快一些,直到 Ryzen 都是这种情况。(第一代 AMD64 芯片是 AMD 的 K8 微架构;请参阅https://agner.org/optimize/获取指令表。)

在 x86-64 中使用 32 位寄存器/指令的优点

x86-64 是 AMD 在 2000 年左右设计的,即 AMD64。英特尔致力于安腾,但没有参与;x86-64 的所有设计决策都是由 AMD 架构师做出的。

AMD64 在编写 32 位寄存器时设计为隐式零扩展,因此可以有效地使用 32 位操作数大小,而不会出现 8 位和 16 位模式下的部分寄存器恶作剧

TL:DR:CPU 有充分的理由希望以某种方式提供 32 位操作数大小,并且 C 类型系统具有易于访问的 32 位类型。 使用int它是很自然的。

如果您想要64 位操作数大小,请使用它。(然后将其描述为long long[u]int64_t,如果您正在为您的 asm 全局变量或函数原型编写 C 声明)。没有什么能阻止您(除了需要您以前可能没有的 REX 前缀的代码量更大)。


所有这些都是与 x86-64 机器代码如何编码 32 位操作数大小完全不同的问题。

AMD 选择将 32 位设为默认值,而 64 位操作数大小需要 REX 前缀。

他们本可以另辟蹊径,将 64 位操作数大小设为默认值,要求 REX.W=0 将其设置为 32,或0x66操作数大小将其设置为 16。这可能导致代码的机器代码更小如果不需要 r8..r15,它主要操作必须是 64 位的东西(通常是指针)。

使用 r8..r15 也需要 REX 前缀(即使作为寻址模式的一部分),因此需要大量寄存器的代码通常会发现自己在大多数指令上使用 REX 前缀,即使使用默认操作数 -尺寸。

很多代码确实int用于很多东西,所以 32 位操作数大小并不少见。如上所述,它有时会更快。 所以让最快的指令最紧凑是有道理的(如果你避免使用 r8d..r15d)。

如果相同的操作码在 32 位和 64 位模式下以相同的方式解码而没有前缀,它也可能让解码器硬件更简单。 我认为这是 AMD 做出这种设计选择的真正动机。他们当然可以清理很多 x86 缺陷,但选择不这样做,可能也是为了保持解码更类似于 32 位模式。

看看您是否会为默认操作数大小为 64 位的 x86-64 版本保存整体代码大小可能会很有趣。例如调整编译器并编译一些现有的代码库。但是,您可能希望教它的优化器支持 64 位操作数而不是 32 位的传统寄存器 RAX..RDI,以尽量减少需要 REX 前缀的指令数量。

(即使您只关心低 32 位,许多指令add也可以安全地用于 64 位操作数大小,尽管高位垃圾会影响 FLAGS 结果。)imul reg,reg


回复:评论中的错误信息:与 32 位机器代码兼容与此无关。 64 位模式与现有的 32 位机器码二进制不兼容;这就是 x86-64 引入新模式的原因。64 位内核在兼容模式下运行 32 位二进制文​​件,其中解码的工作方式与 32 位保护模式完全相同。

https://en.wikipedia.org/wiki/X86-64#OPMODES有一个有用的模式表,包括长模式(以及 64 位与 32 和 16 位兼容模式)与传统模式(如果你启动一个不支持 x86-64 的内核)。

在 64 位模式下,一些操作码是不同的,对于push/pop和其他堆栈指令操作码,操作数大小默认为 64 位。

在该模式下,32 位机器代码将无法正确解码。eg处于兼容0x40模式inc eax,但 REX 前缀处于 64 位模式。请参阅在运行时检测 64 位模式的 x86-32 / x86-64 多语言机器代码片段?例如。

64 位模式解码大多类似地是在解码器中共享晶体管的问题,而不是二进制兼容性。03 add r, r/m据推测,对于解码器来说,对于像,而不是 3 这样的操作码只有 2 个与模式相关的默认操作数大小(16 位或 32 位)会更容易。只有像push/pop这样的操作码的特殊情况才能保证它。(另请注意,REX.W=0 不允许进行编码push r32;操作数大小保持在 64 位。)

AMD 的设计决策似乎一直专注于尽可能多地共享解码器晶体管,也许以防万一 AMD64 没有流行起来,并且在没有人使用它的情况下被困在支持它的情况下。

他们本可以做很多微妙的事情来消除 x86 令人讨厌的遗留怪癖,例如setcc在 64 位模式下创建一个 32 位操作数大小的指令,以避免首先需要异或归零。或者 CISC 的烦恼,比如在零计数移位后标志保持不变(尽管 AMD CPU 比英特尔更有效地处理这个问题,所以他们可能故意把它留在里面。)

或者他们认为细微的调整可能会损害 asm 源代码移植,或者在短期内使编译器后端更难支持 64 位代码生成。

于 2020-01-21T23:26:01.743 回答