Assembly x86_64寄存器rax/eax/ax/al覆盖完整寄存器内容
正如广泛宣传的那样,现代x86_64处理器具有64位寄存器,可作为32位寄存器、16位寄存器甚至8位寄存器向后兼容使用,例如:Assembly x86_64寄存器rax/eax/ax/al覆盖完整寄存器内容,assembly,x86-64,cpu-registers,zero-extension,Assembly,X86 64,Cpu Registers,Zero Extension,正如广泛宣传的那样,现代x86_64处理器具有64位寄存器,可作为32位寄存器、16位寄存器甚至8位寄存器向后兼容使用,例如: 0x1122334455667788 ================ rax (64 bits) ======== eax (32 bits) ==== ax (16 bits) == ah (8 bits) == al (8 bits) 这种方
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位
因此,我有两个问题:
eax
上写东西总是会抹掉rax
,还是更复杂?它适用于所有64位寄存器,还是存在一些例外
A提到了相同的行为,但遗憾的是,再次没有确切的文档参考
换句话说,我想要一个指向指定此行为的文档的链接英特尔/AMD处理器手册中所描述的处理器模型对于现代内核的真正执行引擎来说是一个非常不完美的模型。特别是,处理器寄存器的概念与实际情况不符,不存在EAX或RAX寄存器 指令解码器的一项主要工作是将传统x86/x64指令转换为微操作,即类似RISC处理器的指令。易于并发执行并能够利用多个子执行单元的小型指令。允许同时执行多达6条指令 为了实现这一点,处理器寄存器的概念也被虚拟化了。指令解码器从大量寄存器中分配寄存器。当指令失效时,动态分配寄存器的值被写回当前持有RAX值的寄存器 为了使这些操作能够顺利高效地工作,允许许多指令同时执行,这些操作不具有相互依赖性是非常重要的。最糟糕的是寄存器值依赖于其他指令。EFLAGS寄存器臭名昭著,许多指令都会修改它
你喜欢的工作方式也有同样的问题。大问题是,指令失效时需要合并两个寄存器值。创建将阻塞核心的数据依赖关系。通过将上32位强制为0,该依赖关系立即消失,不再需要合并。Warp 9执行速度。相关:这是一个极好的问题。我刚刚投了赞成票。用户GreyCat,请再次查看第二段代码,并对第五行多加注意。我认为你的评论可能是错误的。我认为是:
;eax=0x11110044
“32位操作的结果隐式零扩展到64位值。这不同于16位和8位操作,不会影响寄存器的上部”一些“类似”问题:我希望看到这个答案与副本的“根”合并。这是一个非常好的视角,有助于解释设计选择。具有物理寄存器文件的微体系结构(如Intel SnB系列和AMD)在退役之前将结果写入物理寄存器。我认为您的部分答案可能只适用于Intel的P6系列(ppro到nehalem),它通过ROB中的值而不是物理寄存器的引用来保存临时结果。(P6在读取太多最近未写入的寄存器时会出现寄存器读取暂停。SnB会完全删除该暂停。)re:EFLAGS,确保大多数指令都会写入它,但很少有指令会读取它。您已经提到了寄存器重命名,这使得。当然,x86从来没有这么简单:一些指令保留一些未修改的标志,因此CPU必须分别重命名EFLAG的不同部分。P4没有这样做,给inc
一个依赖于最后一条指令的修改标志。即使P4已经失效,一些优化指南仍然建议避免使用inc
。此外,当UOP离开前端的顺序队列并进入无序核心时,寄存器重命名也会在发布时间发生。这是解码后的几个阶段。更重要的是,在具有uop缓存和/或循环缓冲区的CPU上,同一条指令可以在解码一次后多次发出/重命名。您在这个答案中的过度简化是对现实的一个有用的近似,因此您可以在不包含所有内容的情况下得出零扩展打破错误依赖的结论:PIt对我来说非常不清楚这与17年前做出的架构决策有何关联。请发表你自己的答案。