为什么在 64 模式下默认操作数大小为 32 位?
Why is default operand size 32 bits in 64 mode?
我正在阅读英特尔文档,卷。 1和3.6.1有一章
64 位模式下的操作数大小和地址大小。共有三个前缀REX.W
、操作数大小66
和地址大小67
前缀。并且提到操作数默认为 32 位大小。并且只能用 REX.W
指令前缀(在其他前缀之后)更改它以使其长 64 位。
我不知道为什么,为什么我不能使用完整的 64 位 space 作为 int
操作数?跟符号有关系吗?或者为什么会有这个限制? (因此,C unsigned int
是否使用 REX.W
前缀对 int 进行操作(正如还提到的那样,前缀仅针对特定指令持续,而不针对整个段,这应该是(大小,地址或操作数)默认值并包含在段描述符中)。
我理解的对吗?
TL:DR:您有 2 个不同的问题。 1 个关于 C 类型大小,另一个关于 x86-64 机器代码如何编码 32 位和 64 位操作数大小。编码的选择是相当随意的,可能会有所不同。但是 int
是 32 位的,因为这是编译器开发人员选择的,与机器代码无关。
int
是 32 位的,因为这仍然是一个有用的大小。它使用 int64_t
一半的内存带宽/缓存占用空间。大多数 64 位 ISA 的 C 实现都有 32 位 int
,包括 x86-64 的主流 ABI(x86-64 System V 和 Windows)。在 Windows 上,甚至 long
也是 32 位类型,大概是为了与为 32 位编写的代码兼容,这些代码对类型大小做了假设。
此外,当时 AMD 的整数乘法器对于 32 位比 64 位快一些,直到 Ryzen 之前都是这种情况。 (第一代 AMD64 芯片是 AMD 的 K8 微架构;参见 https://agner.org/optimize/ 指令 tables。)
x86-64 是 AMD 在 ~2000 年设计的,称为 AMD64。英特尔致力于安腾,但没有参与; x86-64 的所有设计决策均由 AMD 架构师做出。
AMD64 设计为在写入 32 位寄存器时隐式零扩展,因此可以有效地使用 32 位操作数大小 with none of the partial-register shenanigans you get with 8 and 16-bit mode.
TL:DR:CPU 有充分的理由希望以某种方式提供 32 位操作数大小,并且 C 类型系统有一个易于访问的 32 位类型。 使用 int
是很自然的。
如果您想要 64 位操作数大小,请使用它。 (然后向 C 编译器描述它为 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 位的东西(通常是指针)
REX 前缀也需要完全使用 r8..r15(即使作为寻址模式的一部分),因此需要大量寄存器的代码通常会发现自己在大多数指令上都使用 REX 前缀,即使在使用默认的操作数大小。
很多代码确实使用 int
来处理很多东西,所以 32 位操作数大小并不少见。如上所述,它有时会更快。 所以让最快的指令变得最紧凑是有意义的(如果你避免 r8d..r15d)。
如果相同的操作码在 32 位和 64 位模式下以相同的方式解码而没有前缀,它也可能让解码器硬件更简单。我认为这是 AMD 的真正动机对于这个设计选择。他们当然可以清除很多 x86 缺陷,但选择不这样做,可能也是为了保持解码更类似于 32 位模式。
看看您是否会为默认操作数大小为 64 位的 x86-64 版本节省总体代码大小可能会很有趣。例如调整编译器并编译一些现有的代码库。不过,您可能希望教它的优化器支持旧寄存器 RAX..RDI 用于 64 位操作数而不是 32 位操作数,以尽量减少需要 REX 前缀的指令数量。
(许多指令,如 add
或 imul reg,reg
可以安全地以 64 位操作数大小使用,即使您只关心低位 32,尽管高位垃圾会影响 FLAGS 结果.)
回复:评论中的错误信息:与 32 位机器代码兼容与此无关。 64 位模式与现有的 32 位机器代码二进制不兼容;这就是 x86-64 引入新模式 的原因。 64 位内核 运行 兼容模式下的 32 位二进制文件,解码工作与 32 位保护模式完全相同。
https://en.wikipedia.org/wiki/X86-64#OPMODES 有一个有用的 table 模式,包括长模式(以及 64 位与 32 位和 16 位兼容模式)与传统模式(如果你引导内核是不支持 x86-64)。
在 64 位模式下,一些操作码不同,push
/pop
和其他堆栈指令操作码的操作数大小默认为 64 位。
32 位机器代码在该模式下无法正确解码。例如0x40
在兼容模式下是 inc eax
但在 64 位模式下是 REX 前缀。有关示例,请参阅 。
还有
64 位模式解码主要是解码器中共享晶体管的问题,而不是二进制兼容性。大概解码器只有 2 个模式相关的默认设置更容易03 add r, r/m
, not 3. Only special-casing for opcodes like push
/pop
that warrant it. (Also note that REX.W=0 does not let you encode push r32
等操作码的操作数大小(16 位或 32 位);操作数大小保持在 64 位。)
AMD 的设计决策似乎一直专注于尽可能多地共享解码器晶体管,也许是为了防止 AMD64 没有流行起来并且他们在没有人使用它的情况下一直支持它。
他们本可以做很多微妙的事情来消除 x86 令人讨厌的遗留怪癖,例如使 setcc
在 64 位模式下成为 32 位操作数大小的指令以避免首先需要异或归零。或者 CISC 的烦恼,比如在零计数移位后标志保持不变(尽管 AMD CPU 比 Intel 处理得更有效,所以也许他们故意把它留在里面。)
或者他们认为细微的调整可能会损害 asm 源代码移植,或者在短期内使编译器后端更难支持 64 位代码生成。
我正在阅读英特尔文档,卷。 1和3.6.1有一章
64 位模式下的操作数大小和地址大小。共有三个前缀REX.W
、操作数大小66
和地址大小67
前缀。并且提到操作数默认为 32 位大小。并且只能用 REX.W
指令前缀(在其他前缀之后)更改它以使其长 64 位。
我不知道为什么,为什么我不能使用完整的 64 位 space 作为 int
操作数?跟符号有关系吗?或者为什么会有这个限制? (因此,C unsigned int
是否使用 REX.W
前缀对 int 进行操作(正如还提到的那样,前缀仅针对特定指令持续,而不针对整个段,这应该是(大小,地址或操作数)默认值并包含在段描述符中)。
我理解的对吗?
TL:DR:您有 2 个不同的问题。 1 个关于 C 类型大小,另一个关于 x86-64 机器代码如何编码 32 位和 64 位操作数大小。编码的选择是相当随意的,可能会有所不同。但是 int
是 32 位的,因为这是编译器开发人员选择的,与机器代码无关。
int
是 32 位的,因为这仍然是一个有用的大小。它使用 int64_t
一半的内存带宽/缓存占用空间。大多数 64 位 ISA 的 C 实现都有 32 位 int
,包括 x86-64 的主流 ABI(x86-64 System V 和 Windows)。在 Windows 上,甚至 long
也是 32 位类型,大概是为了与为 32 位编写的代码兼容,这些代码对类型大小做了假设。
此外,当时 AMD 的整数乘法器对于 32 位比 64 位快一些,直到 Ryzen 之前都是这种情况。 (第一代 AMD64 芯片是 AMD 的 K8 微架构;参见 https://agner.org/optimize/ 指令 tables。)
x86-64 是 AMD 在 ~2000 年设计的,称为 AMD64。英特尔致力于安腾,但没有参与; x86-64 的所有设计决策均由 AMD 架构师做出。
AMD64 设计为在写入 32 位寄存器时隐式零扩展,因此可以有效地使用 32 位操作数大小 with none of the partial-register shenanigans you get with 8 and 16-bit mode.
TL:DR:CPU 有充分的理由希望以某种方式提供 32 位操作数大小,并且 C 类型系统有一个易于访问的 32 位类型。 使用 int
是很自然的。
如果您想要 64 位操作数大小,请使用它。 (然后向 C 编译器描述它为 long long
或 [u]int64_t
,如果您正在为您的 asm 全局变量或函数原型编写 C 声明)。没有什么能阻止你(除了更大的代码大小,因为你以前可能不需要 REX 前缀)。
所有这些都是一个与 x86-64 机器代码如何编码 32 位操作数大小完全不同的问题。
AMD 选择将 32 位设置为默认值,而 64 位操作数大小需要 REX 前缀。
他们本可以采取另一种方式,将 64 位操作数大小设置为默认值,要求 REX.W=0 将其设置为 32,或 0x66
操作数大小将其设置为16. 如果不需要 r8..r15.
REX 前缀也需要完全使用 r8..r15(即使作为寻址模式的一部分),因此需要大量寄存器的代码通常会发现自己在大多数指令上都使用 REX 前缀,即使在使用默认的操作数大小。
很多代码确实使用 int
来处理很多东西,所以 32 位操作数大小并不少见。如上所述,它有时会更快。 所以让最快的指令变得最紧凑是有意义的(如果你避免 r8d..r15d)。
如果相同的操作码在 32 位和 64 位模式下以相同的方式解码而没有前缀,它也可能让解码器硬件更简单。我认为这是 AMD 的真正动机对于这个设计选择。他们当然可以清除很多 x86 缺陷,但选择不这样做,可能也是为了保持解码更类似于 32 位模式。
看看您是否会为默认操作数大小为 64 位的 x86-64 版本节省总体代码大小可能会很有趣。例如调整编译器并编译一些现有的代码库。不过,您可能希望教它的优化器支持旧寄存器 RAX..RDI 用于 64 位操作数而不是 32 位操作数,以尽量减少需要 REX 前缀的指令数量。
(许多指令,如 add
或 imul reg,reg
可以安全地以 64 位操作数大小使用,即使您只关心低位 32,尽管高位垃圾会影响 FLAGS 结果.)
回复:评论中的错误信息:与 32 位机器代码兼容与此无关。 64 位模式与现有的 32 位机器代码二进制不兼容;这就是 x86-64 引入新模式 的原因。 64 位内核 运行 兼容模式下的 32 位二进制文件,解码工作与 32 位保护模式完全相同。
https://en.wikipedia.org/wiki/X86-64#OPMODES 有一个有用的 table 模式,包括长模式(以及 64 位与 32 位和 16 位兼容模式)与传统模式(如果你引导内核是不支持 x86-64)。
在 64 位模式下,一些操作码不同,push
/pop
和其他堆栈指令操作码的操作数大小默认为 64 位。
32 位机器代码在该模式下无法正确解码。例如0x40
在兼容模式下是 inc eax
但在 64 位模式下是 REX 前缀。有关示例,请参阅
还有
64 位模式解码主要是解码器中共享晶体管的问题,而不是二进制兼容性。大概解码器只有 2 个模式相关的默认设置更容易03 add r, r/m
, not 3. Only special-casing for opcodes like push
/pop
that warrant it. (Also note that REX.W=0 does not let you encode push r32
等操作码的操作数大小(16 位或 32 位);操作数大小保持在 64 位。)
AMD 的设计决策似乎一直专注于尽可能多地共享解码器晶体管,也许是为了防止 AMD64 没有流行起来并且他们在没有人使用它的情况下一直支持它。
他们本可以做很多微妙的事情来消除 x86 令人讨厌的遗留怪癖,例如使 setcc
在 64 位模式下成为 32 位操作数大小的指令以避免首先需要异或归零。或者 CISC 的烦恼,比如在零计数移位后标志保持不变(尽管 AMD CPU 比 Intel 处理得更有效,所以也许他们故意把它留在里面。)
或者他们认为细微的调整可能会损害 asm 源代码移植,或者在短期内使编译器后端更难支持 64 位代码生成。