将EAX复制到RAX更高位?

时间:2018-02-02 17:09:24

标签: assembly x86-64

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

提前谢谢。

3 个答案:

答案 0 :(得分:7)

我的尝试......来自演示派对的音乐作品(或者更有可能来自旅行)让我头疼,所以我放弃了xchg al,ah用来复制31位并试图保存1位符号,然后修补中间结果,因为事实证明我有几个问题没有预见。

所以我反而选择了鸡蛋,并且只复制2x 16位字,然后以Jester的方式重新洗牌(当他发布答案时,我即将张贴 ; 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 的想法,我发誓)。

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 指令有趣):

{{1}}

答案 1 :(得分: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

答案 2 :(得分:2)

很难有效地执行此操作。评论中唯一不利于性能的建议是使用imul的64位常量:

; 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

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

你不能用imul r64, r64, imm32执行此操作,因为我们的常量有33位。

如果你不想要一个常规的内存常量,你可以将常量与指令一起内联,然后跳过它。

; 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:

这是一个独立的与位置无关的x86-64机器代码片段的汇编,它将EAX广播到两半的RAX。从正确性POV来看,这与使用immediates的说明没有区别。 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:

它确实从与代码相同的页面进行数据加载,但AFAIK无法使页面可执行但页面表设置无法读取。它会耗尽L1D缓存中的缓存行,dTLB条目,以及L1I缓存和iTLB条目。

Jester在2个商店/ 1重新加载的评论中的建议是紧凑的,但会导致所有CPU上的商店转发停顿,除了按顺序Atom(前Silvermont):

push rax
mov  [rsp+4], eax     ; overwrite high bytes
pop  rax              ; store-forwarding stall when a wide load covers 2 narrow stores

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

如果表现很重要,可能会有必要避免需要完全你提出的问题。

E.g。保存/恢复其他15个通用寄存器中的一个,无论您正在做什么,都可以将其用作临时寄存器。理想情况下围绕整个函数,所以你可以使用scratch reg在循环内进行广播。或者只是在没有先保存的情况下使用clobber,如果它是您可以快速重新加载或重新计算的东西。

; rax = garbage:eax
push   rbx            ; save/restore if needed

mov    ebx, eax
;mov    eax, eax       ; zero-extend eax into rax if needed
shl    rbx, 32
or     rax, rbx

pop    rbx

对于具有零延迟整数MOV(Ivybridge +和Ryzen)的CPU,这有2个周期延迟(对于rax)。它将存储/重新加载到RBX的关键路径中,因此如果您实际上必须围绕每个广播执行此操作,请明智地选择您的临时注册,而不是仅为整个循环释放额外的寄存器。

或者使用BMI2,rorx rcx, rax, 32 / or rax, rcx(RAX的高32位必须已经归零,因此您可以复制+旋转而不是移位)。 (如果它有BMI2,挖掘机也只有2c延迟。对于整数regs,它没有零延迟mov,只有矢量)。

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

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

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

; movq       rax, xmm0

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

如果您这样做,将RAX复制到XMM0或从XMM0复制是大部分成本。这会破坏xmm0,根据你是否完全没有其他寄存器,或者没有整数寄存器,它可能会或可能不会正常。

但如果你整个时间都使用xmm0作为整数值,那么你可以避免这种代价。您可以在xmm寄存器中执行标量整数数学运算(忽略高位字节发生的情况)。可以使用paddd / paddqpslldqpmuludq / pand等说明。你不能做的主要是在寻址模式下使用它,但如果你正在广播低32位,这可能不是一个地址或索引。