Her*_*man 1 64-bit assembly x86-64 machine-code instruction-encoding
我正在阅读英特尔文档,第一卷。1 和3.6.1 64 位模式下的操作数大小和地址大小一章。有三个前缀REX.W,操作数大小66和地址大小67前缀。并提到操作数的大小默认为 32 位。并且只能用REX.W指令前缀(在其他前缀之后)以使其长度为 64 位。
我不知道为什么会这样,为什么我不能将完整的 64 位空间用于例如int操作数?跟签有关系吗?或者为什么会有这个限制?(所以,C是否在 int 上unsigned int使用REX.W前缀操作(正如也提到的,前缀仅持续特定指令,而不是整个段,这应该是(大小,地址或操作数)默认值和包含在段描述符中)。
我理解正确吗?
TL:DR:你有两个不同的问题。1 关于 C 类型大小,另一个关于 x86-64 机器代码如何编码 32 与 64 位操作数大小。编码选择是相当随意的,可以有所不同。但它int是 32 位的,因为这是编译器开发人员选择的,与机器代码无关。
int是 32 位,因为这仍然是一个有用的大小。它使用的内存带宽/缓存占用是int64_t. 大多数 64 位 ISA 的 C 实现都有 32 位int,包括 x86-64(x86-64 System V 和 Windows)的主流 ABI。在 Windows 上,evenlong是 32 位类型,大概是为了与为 32 位编写的代码兼容,这些代码对类型大小进行了假设。
此外,当时 AMD 的整数乘法器在 32 位上比 64 位要快一些,直到 Ryzen 都是这种情况。(第一代 AMD64 芯片是 AMD 的 K8 微架构;有关指令表,请参阅https://agner.org/optimize/。)
x86-64 是由 AMD 在 2000 年左右设计的,即 AMD64。英特尔致力于安腾而不参与;x86-64 的所有设计决策都是由 AMD 架构师做出的。
AMD64 设计为在写入 32 位寄存器时使用隐式零扩展,因此可以有效地使用 32 位操作数大小,而不会出现您在 8 位和 16 位模式下获得的部分寄存器恶作剧。
TL:DR:CPU 有充分的理由希望以某种方式提供 32 位操作数大小,并且 C 类型系统有一个易于访问的 32 位类型。 使用int它是很自然的。
如果您想要64 位操作数大小,请使用它。(如果您正在为 asm 全局变量或函数原型编写 C 声明,则将其描述为 C 编译器作为long long或[u]int64_t)。没有什么能阻止你(除了因为需要 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 位操作数的旧寄存器 RAX..RDI 而不是 32 位,以尽量减少需要 REX 前缀的指令数量。
(即使您只关心低 32,许多指令喜欢add或imul reg,reg可以安全地使用 64 位操作数大小,尽管高垃圾会影响 FLAGS 结果。)
回复:评论中的错误信息:与 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 位机器代码将无法正确解码。例如0x40,inc eax在兼容模式下,但在 64 位模式下是 REX 前缀。请参阅在运行时检测 64 位模式的 x86-32 / x86-64 多语言机器代码片段?举个例子。
还
64 位模式解码大多类似地是在解码器中共享晶体管的问题,而不是二进制兼容性。 据推测,对于解码器来说,对于像这样的操作码03 add r, r/m,只有 2 个依赖于模式的默认操作数大小(16 位或 32 位)更容易,而不是 3。只有像push/pop这样的操作码的特殊外壳才能保证它。(另请注意,REX.W = 0时不帮助你编码push r32;操作数大小停留在64位)
AMD 的设计决策似乎一直专注于尽可能多地共享解码器晶体管,以防万一 AMD64 没有流行起来,并且在没有人使用它的情况下坚持支持它。
他们本可以做很多微妙的事情来消除 x86 令人讨厌的遗留怪癖,例如setcc在 64 位模式下制作32 位操作数大小的指令,以避免首先需要异或归零。或者 CISC 的烦恼,比如在零计数转换后标志保持不变(尽管 AMD CPU 比英特尔更有效地处理这个问题,所以也许他们故意把它留在里面。)
或者他们认为细微的调整可能会损害 asm 源代码移植,或者在短期内使编译器后端更难支持 64 位代码生成。
| 归档时间: |
|
| 查看次数: |
621 次 |
| 最近记录: |