将EAX复制到RAX更高位?

mas*_*sec 4 assembly x86-64

我想知道是否有任何指令序列,而不使用任何其他寄存器将RAX的低32位复制到其高32位.当然,我也希望EAX完好无损.

先感谢您.

Ped*_*d7g 7

我的尝试......来自演示派对上的音乐组合(或者更有可能来自旅行)让我头疼,所以我放弃了imul rax,rax,imm32用于复制31位并尝试将1位保存在符号中然后修补中间结果,因为它有几个问题,我没有预见到.

所以我反而选择了鸡蛋,并且只复制2x 16位的单词,然后以一种Jester的方式进行重新洗牌(xchg al,ah我发誓当他发布答案的时候,我发誓).

    ; rax =                           00001234 (bytes, not hexa digits)
    ror     rax, 16                 ; 34000012
    imul    rax, rax, 0x00010001    ; 34001212
    shr     rax, 16                 ; 00340012
    imul    rax, rax, 0x00010001    ; 34341212
    ror     rax, 24                 ; 21234341
    xchg    al, ah                  ; 21234314
    ror     rax, 8                  ; 42123431
    xchg    al, ah                  ; 42123413
    rol     rax, 16                 ; 12341342
    xchg    al, ah                  ; 12341324
    ror     rax, 8                  ; 41234132
    xchg    al, ah                  ; 41234123
    rol     rax, 8                  ; 12341234
Run Code Online (Sandbox Code Playgroud)

一个较短的(指令计数)变体(rol ...,8从第5个开始只有使用指令的有趣转折):

    ; eax =                           00001234 (bytes, not hexa digits)
    ror     rax, 8                  ; 40000123
    imul    rax, rax, 0x01000001    ; 40123123
    rol     rax, 16                 ; 12312340
    mov     al, ah                  ; 12312344
    rol     rax, 8                  ; 23123441
    rol     ax, 8                   ; 23123414
    rol     rax, 8                  ; 31234142
    rol     ax, 8                   ; 31234124
    rol     rax, 8                  ; 12341243
    rol     ax, 8                   ; 12341234
Run Code Online (Sandbox Code Playgroud)


Jes*_*ter 5

必须有一个更简单的方法:-D

shl rax, 8   ; 00012340
mov al, ah   ; 00012344
bswap rax    ; 44321000
shr rax, 16  ; 00443210
mov al, ah   ; 00443211
ror rax, 8   ; 10044321
xchg ah, al  ; 10044312
rol rax, 8   ; 00443121
xchg ah, al  ; 00443112
shl rax, 8   ; 04431120
mov al, ah   ; 04431122
ror rax, 32  ; 11220443
xchg ah, al  ; 11220434
ror rax, 8   ; 41122043
xchg ah, al  ; 41122034
ror rax, 8   ; 44112203
mov ah, al   ; 44112233
ror rax, 8   ; 34411223
xchg ah, al  ; 34411232
rol rax, 16  ; 41123234
xchg ah, al  ; 41123243
ror rax, 8   ; 34112324
xchg ah, al  ; 34112342
rol rax, 24  ; 12342341
xchg ah, al  ; 12342314
ror rax, 8   ; 41234231
xchg ah, al  ; 41234213
ror rax, 8   ; 34123421
xchg ah, al  ; 34123412
ror rax, 16  ; 12341234
Run Code Online (Sandbox Code Playgroud)


Pet*_*des 5

如果不按要求接触第二个寄存器,就很难有效地做到这一点。评论中唯一不会影响性能的建议是使用 64 位常量imul

   ; mov    eax,eax     ; if eax isn't already zero-extended into rax
imul      rax, [rel broadcast_low32_multiplier]

section .rodata
  broadcast_low32_multiplier:   dq  0x100000001
Run Code Online (Sandbox Code Playgroud)

这在 Intel Sandybridge 系列 CPU 和 AMD Ryzen 上具有每时钟 1 个吞吐量(和 3 个周期延迟)。但是推土机系列每 4 个时钟只有一个。( http://agner.org/optimize/ , https://uops.info/ )

你不能用 来做这件事imul r64, r64, imm32,因为我们的常量有 33 位。 您可以将 0x100000001 分解为0x663d81 * 0x281,并在具有快速乘法器的 CPU 上以 2 uop 和 6 周期延迟进行。(谢谢,@stepan

     ;mov  ecx, eax       ; zero extend into a different reg with 0 latency, and imul from there, if not already zero-extended
 imul     rax, rax, 0x281
 imul     rax, rax, 0x663d81     ;     0x281 * 0x663d81 = 0x100000001
Run Code Online (Sandbox Code Playgroud)

制作一个完全独立的 x86-64 机器代码片段的另一种选择是将常量内联按照说明跳过它。

    ; mov eax,eax      ; if eax isn't already zero-extended into rax
 imul      rax, [rel broadcast_low32_multiplier]
 jmp       after_constant
   broadcast_low32_multiplier:   dq  0x100000001
after_constant:
Run Code Online (Sandbox Code Playgroud)

从正确性 POV 来看,这与使用立即数的指令没有区别。NASM 列表:

 1 00000000 480FAF0502000000          imul    rax, [rel broadcast_low32_multiplier]
 2 00000008 EB08                      jmp       after_constant
 3 0000000A 0100000001000000          broadcast_low32_multiplier:   dq  0x100000001
 4                                    after_constant:
Run Code Online (Sandbox Code Playgroud)

它确实从与代码相同的页面加载数据,但 AFAIK 不可能使页面可执行但无法使用页表设置读取,因此在纯指令可以工作的情况下,这不会失败。不过,它将用完 L1D 缓存中的一条缓存线、一个 dTLB 条目以及 L1i 缓存和一个 iTLB 条目。它甚至可能会错过在DTLB条目是否只在ITLB热,可能会错过L1D缓存(但很可能砸在L2高速缓存;最CPU有一些非排他性的统一高速缓存是取指经过看到的。更多混合代码和数据的性能缺乏好处。)


存储/重新加载:紧凑但高延迟

Jester 在 2 个 store / 1 reload 的评论中的建议是紧凑的,但会导致所有 CPU 上store-forwarding 停顿,除了 in-order Atom(pre Silvermont):

push rax
mov  [rsp+4], eax     ; overwrite high bytes
pop  rax              ; store-forwarding stall when a wide load covers 2 narrow stores
Run Code Online (Sandbox Code Playgroud)

这可能是最小的总大小版本,特别是如果您有一个堆栈帧,可以让您使用类似的rbp+disp8寻址模式[rbp-12],与rsp-relative相比节省 1 个字节(即使没有索引寄存器也需要一个 SIB)。


标准方式更快,使用第二个寄存器

(“另一种”标准方式是mov rcx, 0x100000001/ imul rax, rcx,但 64 位立即数很大,而 shift/OR 实际上是较低的延迟。)

如果性能很重要,那么避免完全按照您的要求去做可能是值得的。

例如,无论您在做什么,都可以保存/恢复其他 15 个通用寄存器之一,因此您可以将其用作临时寄存器。理想情况下围绕整个函数,因此您可以使用临时寄存器在循环内进行此广播。或者只是在不先保存的情况下进行破坏,如果您可以快速重新加载或重新计算。

; rax = garbage:eax
push   rbx            ; save/restore if needed, preferably at the top of the function

mov    ebx, eax       ; clear high garbage with 32-bit zero extend
shl    rax, 32        ; clear low garbage via shifting
or     rax, rbx        ; or  lea rcx, [rax+rbx]  into a 3rd reg if you want

...
pop    rbx             ; preferably at the bottom of the function
Run Code Online (Sandbox Code Playgroud)

即使没有移动消除,这也有 2 个周期的延迟(对于 rax)。我们将原始 RAX 与复制(零扩展)并行移动到 RBX。如果 EAX 已经零扩展,则可以在 RBX 中移动副本,如果您正在优化具有 mov-elimination 的 CPU:当您覆盖 mov 目标时,英特尔的实现会释放 mov-elimination 跟踪资源。但这会使 CPU 的延迟更高,而无需消除移动,从而将 mov 置于关键路径上。(冰湖的微代码更新禁用了移动消除。/叹气。)

push/pop 将存储/重新加载引入到 RBX 的关键路径中,因此如果您实际上必须在每次广播时执行此操作,请明智地选择您的临时寄存器,而不是仅仅为整个循环或函数释放额外的寄存器。

或者使用 BMI2,rorx rcx, rax, 32/or rax, rcx只需 2 uop即可完成工作。(与 mov/shift 不同,为此必须已经将 RAX 的高 32 位清零)。


或者使用 SSE2(x86-64 的基线)。如果您可以首先使用 xmm0 而不是 rax :

; movd       xmm0, eax      ; instead of whatever was setting rax

punpckldq  xmm0, xmm0        ; [dcba] -> [bbaa]

; movq       rax, xmm0
Run Code Online (Sandbox Code Playgroud)

大多数 CPU 上的 1 个周期延迟(SlowShuffle Core2 / K8 除外,您可以使用pshuflw正确的 imm8 来提高效率),加上使用 xmm0 而不是 rax 的任何额外成本。

如果您这样做,将 RAX 复制到 XMM0 或从 XMM0 复制是这里的大部分成本,例如 4 到 6 次循环往返,取决于微体系结构,在 shuffle 之上。(例如 uops.info 的Zen2 延迟测试包括以 6 个周期测量movd/movq 往返)。这会破坏 xmm0,这可能好也可能不好。

但是,如果您一直使用 xmm0 作为整数值,则可以避免该成本。您可以在 xmm 寄存器中进行标量整数数学运算(忽略高位字节会发生什么)。可以使用paddd/ paddqpslldqpmuludq/pand等指令。你不能做的主要事情是在寻址模式下使用它,但如果你广播低 32 位,这可能不是地址或索引。

  • 0x100000001 == 0x663d81 * 0x281 所以我们可以尝试 `imul rax, rax, 0x663d81` + `imul rax, rax, 0x281` 这对于冷代码可能更好(当数据常量不在 L1 中时):) (2认同)

归档时间:

查看次数:

537 次

最近记录:

7 年,7 月 前