x86_64寄存器rax/eax/ax/al覆盖完整的寄存器内容[重复]
Posted
技术标签:
【中文标题】x86_64寄存器rax/eax/ax/al覆盖完整的寄存器内容[重复]【英文标题】:x86_64 registers rax/eax/ax/al overwriting full register contents [duplicate] 【发布时间】:2014-10-16 19:19:48 【问题描述】:正如广泛宣传的那样,现代 x86_64 处理器具有 64 位寄存器,可以向后兼容的方式用作 32 位寄存器、16 位寄存器甚至 8 位寄存器,例如:
0x1122334455667788
================ rax (64 bits)
======== eax (32 bits)
==== ax (16 bits)
== ah (8 bits)
== al (8 bits)
这种方案可以按字面意思理解,即人们总是可以使用指定的名称访问寄存器的一部分以进行读取或写入目的,并且它是高度合乎逻辑的。事实上,对于 32 位以下的所有内容都是如此:
mov eax, 0x11112222 ; eax = 0x11112222
mov ax, 0x3333 ; eax = 0x11113333 (works, only low 16 bits changed)
mov al, 0x44 ; eax = 0x11113344 (works, only low 8 bits changed)
mov ah, 0x55 ; eax = 0x11115544 (works, only high 8 bits changed)
xor ah, ah ; eax = 0x11110044 (works, only high 8 bits cleared)
mov eax, 0x11112222 ; eax = 0x11112222
xor al, al ; eax = 0x11112200 (works, only low 8 bits cleared)
mov eax, 0x11112222 ; eax = 0x11112222
xor ax, ax ; eax = 0x11110000 (works, only low 16 bits cleared)
然而,一旦我们进入 64 位的东西,事情似乎就相当尴尬了:
mov rax, 0x1111222233334444 ; rax = 0x1111222233334444
mov eax, 0x55556666 ; actual: rax = 0x0000000055556666
; expected: rax = 0x1111222255556666
; upper 32 bits seem to be lost!
mov rax, 0x1111222233334444 ; rax = 0x1111222233334444
mov ax, 0x7777 ; rax = 0x1111222233337777 (works!)
mov rax, 0x1111222233334444 ; rax = 0x1111222233334444
xor eax, eax ; actual: rax = 0x0000000000000000
; expected: rax = 0x1111222200000000
; again, it wiped whole register
这样的行为对我来说似乎非常荒谬和不合逻辑。看起来试图以任何方式向eax
写入任何内容都会导致rax
寄存器的高32 位被擦除。
所以,我有两个问题:
我相信这种尴尬的行为必须记录在某个地方,但我似乎无法在任何地方找到详细的解释(关于 64 位寄存器的高 32 位被擦除的确切程度)。我对eax
的写信总是抹去rax
是对的,还是更复杂的事情?它适用于所有 64 位寄存器,还是有一些例外?
strongly related question 提到了相同的行为,但遗憾的是,再次没有确切的文档引用。
换句话说,我想要一个指向指定此行为的文档的链接。
仅仅是我还是整个事情看起来真的很奇怪和不合逻辑(即 eax-ax-ah-al、rax-ax-ah-al 有一种行为,而 rax-eax 有另一种行为)?可能我在这里遗漏了一些关于为什么要这样实施的关键点?
非常感谢您解释“为什么”。
【问题讨论】:
相关:***.com/q/11177137 这是一个很好的问题。我刚刚投了赞成票。 GreyCat 用户,请再看一遍第二段代码,并对第五行多加注意。我认为您的评论可能是错误的。 我认为是:; eax = 0x11110044
x86-64.org/documentation/assembly.html "32 位运算的结果隐式零扩展为 64 位值。这与 16 位和 8 位运算不同,不会影响寄存器的上半部分"跨度>
一些“类似”问题:***.com/questions/6654098/…***.com/questions/19082108/…***.com/questions/11910501/…
【参考方案1】:
Intel/AMD 处理器手册中记录的处理器模型对于现代内核的真实执行引擎来说是一个相当不完美的模型。特别是处理器寄存器的概念与现实不符,没有 EAX 或 RAX 寄存器之类的东西。
指令解码器的一项主要工作是将传统 x86/x64 指令转换为微操作,即类似 RISC 处理器的指令。易于并发执行并能够利用多个执行子单元的小指令。允许同时执行多达 6 条指令。
为了实现这一点,处理器寄存器的概念也被虚拟化了。指令解码器从一大堆寄存器中分配一个寄存器。当指令退出时,该动态分配的寄存器的值会被写回到当前保存着 RAX 值的任何寄存器。
为了使这项工作顺利有效,允许许多指令同时执行,这些操作不具有相互依赖性是非常重要的。最糟糕的是,寄存器值取决于其他指令。 EFLAGS 寄存器臭名昭著,许多指令对其进行了修改。
你喜欢它的工作方式也有同样的问题。大问题,它需要在指令退出时合并两个寄存器值。创建会阻塞核心的数据依赖项。通过强制高 32 位为 0,这种依赖关系立即消失,不再需要合并。 Warp 9 的执行速度。
【讨论】:
我希望看到这个答案与副本的“根”合并。这是一个非常好的视角,有助于解释设计选择。 具有物理寄存器文件的微架构(例如 Intel SnB 系列和 AMD)在退役之前将结果写入物理寄存器。我认为您的部分答案可能仅适用于英特尔的 P6 系列(ppro to nehalem),它通过 ROB 内部的值而不是通过对物理寄存器的引用来保存临时结果。 (当读取太多未写入的寄存器时,P6 会出现寄存器读取停顿。SnB 完全消除了这一点。) re: EFLAGS,当然大多数指令都会写它,但很少有指令会读它。您已经提到了寄存器重命名,这使得write-after-write dependencies on EFLAGS a non-issue。当然,x86 没有什么比这更简单的了:一些指令没有修改一些标志,因此 CPU 必须分别重命名 EFLAGS 的不同部分。 P4 没有这样做,给inc
依赖于最后一条指令来修改标志。即使 P4 已死,一些优化指南仍然建议避免使用inc
。
另外,寄存器重命名发生在发出时,当微指令离开前端的有序队列并进入无序核心时。这是解码后的几个阶段。更重要的是,在具有 uop 缓存和/或循环缓冲区的 CPU 上,相同的指令可以在解码一次后多次发出/重命名。您在此答案中的过度简化是对现实的有用近似,因此您可以在不包括所有 Agner Fog's microarch PDF 的情况下达到零扩展打破错误依赖关系的目的:P
我很不清楚这与 17 年前做出的架构决策有何关联。请发布您自己的答案。以上是关于x86_64寄存器rax/eax/ax/al覆盖完整的寄存器内容[重复]的主要内容,如果未能解决你的问题,请参考以下文章
将有效地址加载到 x86_64 中的 XMM 寄存器的一条指令?