代码之家  ›  专栏  ›  技术社区  ›  masec

是否将EAX复制到RAX高位?

  •  5
  • masec  · 技术社区  · 6 年前

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

    最好也不使用任何堆栈内存。

    3 回复  |  直到 3 年前
        1
  •  6
  •   Ped7g    6 年前

    我的尝试。。。在这里举行的演示派对上,我的音乐创作让我头痛(或者更可能是在这里旅行),所以我放弃了 imul rax,rax,imm32 用于复制31位,并尝试在符号中保存1位,然后修补中间结果,因为结果表明它有几个我没有预见到的问题。

    所以我选择了回避,只复制2个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
    

    稍短一点的(指令计数)变体(使用 rol ...,8 仅从第五个一开始的说明):

        ; 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
    
        2
  •  5
  •   Jester    6 年前

    必须有一个更简单的方法:-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
    
        3
  •  5
  •   Peter Cordes    3 年前

    如果不按要求触摸第二个寄存器,很难有效地执行此操作 . 注释中唯一不会影响性能的建议是使用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
    

    这在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个周期的延迟完成。( Thanks, @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
    

    生成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:
    

    从正确性角度来看,这与使用立即数的指令没有区别。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:
    

    它确实会从与代码相同的页面加载数据,但在页面表设置下,不可能使页面可执行但不可读,因此在纯指令可以工作的情况下,这是不可能失败的。不过,它将耗尽L1D缓存中的缓存线和dTLB项,以及L1i缓存和iTLB项。如果条目仅在iTLB中是热的,它甚至可能在dTLB中丢失,并且可能在L1d缓存中丢失(但可能在L2缓存中命中;大多数CPU都有一些非独占的统一缓存,可以通过这些缓存进行指令提取。请参阅 this 更多关于性能的信息—混合代码和数据的好处不足。)


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

    Jester在“2个商店/1个换货”评论中的建议很紧凑,但会导致 store-forwarding stall on all CPUs except in-order 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字节vs。 rsp -相对(即使没有索引寄存器,也需要SIB)。


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

    (其他标准方式为 mov rcx, 0x100000001 / imul rax, rcx ,但64位立即数很大,而移位/或实际上是较低的延迟。)

    避免这样做可能是值得的 确切地 如果性能重要,请回答您的问题。

    E、 g.保存/恢复其他15个通用寄存器中的一个,无论您在做什么,都可以将其用作临时寄存器。理想的情况是围绕整个函数,因此可以使用scratch reg在循环内进行广播。或者直接重击而不先保存,如果你可以快速重新加载或重放的话。

    ; 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
    

    这有2个周期的延迟(对于rax),即使没有mov消除。我们将原始RAX与复制(零扩展)并行地转移到RBX中。如果EAX已经过零扩展,那么如果您正在为消除mov的CPU进行优化,则可以在RBX中移动拷贝: Intel's implementation 覆盖mov目标时释放mov消除跟踪资源。但如果不消除mov,CPU的延迟会更高,从而将mov置于关键路径上。(冰湖的微码更新禁用mov消除。/叹气。)

    推送/弹出在RBX的关键路径中引入了存储/重新加载功能,因此,如果您确实必须在每次广播中都这样做,请明智地选择scratch reg,而不是为整个循环或功能释放一个额外的寄存器。

    或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
    

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

    如果您这样做的话,将RAX复制到XMM0或从XMM0复制RAX的成本将是最大的,比如4到6个周期的往返,具体取决于微体系结构,再加上无序排列。(例如uops信息 Zen2 latency tests include a movd/movq round trip measured at 6 cycles ). 这将重击xmm0,这可能没问题,也可能没问题。

    但是,如果您在整数值的整个过程中都使用xmm0,则可以避免这一成本。您可以在xmm寄存器中进行标量整数计算(忽略上层字节的情况)。说明如下 paddd / paddq , pslldq , pmuludq / pand 都可用。您不能做的主要事情是在寻址模式下使用它,但如果您正在广播低32位,这可能不是地址或索引。