x86_64寄存器rax/eax/ax/al覆盖完整寄存器内容

Gre*_*Cat 70 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)
Run Code Online (Sandbox Code Playgroud)

这样的方案可以从字面上理解,即,总是可以使用指定的名称仅访问寄存器的一部分用于读取或写入目的,并且这将是高度逻辑的.实际上,对于高达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)
Run Code Online (Sandbox Code Playgroud)

但是,一旦我们得到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
Run Code Online (Sandbox Code Playgroud)

这种行为对我来说似乎非常荒谬和不合逻辑.看起来试图eax通过任何方式写任何东西导致擦除高32位rax寄存器.

所以,我有两个问题:

  1. 我相信这种尴尬的行为必须记录在某处,但我似乎无法找到详细的解释(64位寄存器的高32位是如何被擦除的).写作eax总是擦拭我是对的rax,还是更复杂的东西?它是否适用于所有64位寄存器,或者有一些例外?

    一个强烈相关的问题提到了相同的行为,但是,唉,再次没有对文档的确切引用.

    换句话说,我想要指向此行为的文档链接.

  2. 它只是我或整个事情似乎真的很奇怪和不合逻辑(即eax-ax-ah-al,rax-ax-ah-al有一种行为,而rax-eax有另一种行为)?可能是我在这里错过了一些重要的观点,为什么它会像那样实现?

    对"为什么"的解释将受到高度赞赏.

Han*_*ant 71

英特尔/ AMD处理器手册中记录的处理器模型对于现代核心的真实执行引擎来说是一个非常不完美的模型.特别是,处理器寄存器的概念与现实不匹配,没有EAX或RAX寄存器这样的东西.

指令解码器的一个主要工作是将传统的x86/x64指令转换为微操作,类似RISC的处理器的指令.易于同时执行并能够利用多个执行子单元的小指令.允许多达6条指令同时执行.

为了实现这一目标,处理器寄存器的概念也是虚拟化的.指令解码器从一大堆寄存器中分配寄存器.当指令退出时,该动态分配的寄存器的值被写回到当前保存的值,例如RAX.

为了使这项工作顺利有效,允许许多指令同时执行,这些操作没有相互依赖性是非常重要的.最糟糕的是,寄存器值取决于其他指令.EFLAGS寄存器是臭名昭着的,许多指令修改它.

喜欢它的工作方式也有同样的问题.很大的问题,它需要在指令退役时合并两个寄存器值.创建一个阻碍核心的数据依赖.通过强制高32位为0,该依赖性立即消失,不再需要合并.Warp 9执行速度.

  • 我希望看到这个答案与副本的"根"合并.这是一个非常好的视角,有助于解释设计选择. (7认同)
  • re:EFLAGS,确定大多数指令都写了它,但很少有指令读它.您已经提到了注册重命名,这使得[EFLAGS上的写后写依赖关系成为非问题](https://en.wikipedia.org/wiki/Register_renaming#Data_hazards).当然,x86没有什么比这更简单:一些指令会保留一些未经修改的标志,因此CPU必须分别重命名EFLAGS的不同部分.P4没有这样做,让`inc`依赖于修改标志的最后一条指令.即使P4已经死了,一些优化指南仍建议避免使用"inc". (4认同)
  • 具有物理寄存器文件的微体系结构(例如Intel SnB系列和AMD)会在退役之前将结果写入物理寄存器.我认为你的答案的一部分可能只适用于英特尔的P6系列(ppro to nehalem),它通过ROB内部的值保存临时结果,而不是通过引用物理寄存器.(当读取太多不是最近写入的寄存器时,P6具有寄存器读取停顿.SnB完全删除了它.) (3认同)
  • 此外,寄存器重命名发生在发布时,当uops离开前端的有序队列并进入无序核心时.这是解码后的几个阶段.更重要的是,在具有uop缓存和/或循环缓冲区的CPU上,在解码一次后可以多次发出/重命名相同的指令.你在这个答案中的过度简化是对现实的一个有用的近似,所以你可以达到关于零扩展打破虚假依赖的观点,而不包括所有[Agner Fog的微架PDF](http://agner.org/optimize/): P (3认同)

归档时间:

查看次数:

50724 次

最近记录:

8 年,10 月 前