如何将128位立即数移动到XMM寄存器

时间:2011-07-11 17:38:36

标签: assembly x86 sse simd

已经有a question这个,但它被关闭为“含糊不清”所以我开了一个新的 - 我找到了答案,也许它也会帮助其他人。

问题是:如何编写汇编代码序列来初始化具有128位立即(常量)值的XMM寄存器?

5 个答案:

答案 0 :(得分:16)

只是想补充一点,可以在Agner Fog的手册Optimizing subroutines in assembly language,生成常量,第13.4节,第121页中使用汇编来阅读有关生成各种常量的信息。

答案 1 :(得分:10)

只需一条movaps指令就可以这样做:

.section .rodata    # put your constants in the read-only data section
.p2align 4          # align to 16 = 1<<4
LC0:
        .long   1082130432
        .long   1077936128
        .long   1073741824
        .long   1065353216

.text
foo:
        movaps  LC0(%rip), %xmm0

通过数据加载加载它通常比将其嵌入指令流更好,特别是因为它需要多少指令。这是CPU执行的几个额外的uop,用于一个任意常量,无法通过几次移位从全部生成。

如果它更容易,你可以在jit-compile的函数之前或之后放置常量,而不是在单独的部分中。但由于CPU已经拆分了L1d / L1i缓存和TLB,因此通常最好将常量与指令分开组合。

如果你的常数的两半都相同,你可以用SSE3广播加载它 movddup (m64), %xmm0

答案 2 :(得分:7)

作为10000种方法之一,请使用SSE4.1 pinsrq

mov    rax, first half
movq   xmm0, rax      ; better than pinsrq xmm0,rax,0 for performance and code-size

mov    rax, second half
pinsrq xmm0, rax, 1

答案 3 :(得分:6)

在指令流中有多种嵌入常量的方法:

  1. 使用立即操作数
  2. 从PC相关地址加载
  3. 因此,虽然无法在XMM寄存器中执行立即加载,但可以从存储的值中进行PC相对加载(64位)“ “到代码执行的地方。这就产生了类似的东西:

    .align 4
    .val:
        .long   0x12345678
        .long   0x9abcdef0
        .long   0xfedbca98
        .long   0x76543210
    func:
         movdqa .val(%rip), %xmm0
    

    当您拆卸时:

    0000000000000000 :
       0:   78 56 34 12 f0 de bc 9a
       8:   98 ca db fe 10 32 54 76
    
    0000000000000010 :
      10:   66 0f 6f 05 e8 ff ff    movdqa -0x18(%rip),%xmm0        # 0 
    

    完全紧凑,23字节。

    其他选项是在堆栈上构造值并再次从那里加载它。在32位x86中,您没有%rip - 相对内存访问,仍然可以在24字节中执行此操作(假设堆栈指针在条目上对齐;否则,需要未对齐的加载):

    00000000 :
       0:   68 78 56 34 12          push   $0x12345678
       5:   68 f0 de bc 9a          push   $0x9abcdef0
       a:   68 98 ca db fe          push   $0xfedbca98
       f:   68 10 32 54 76          push   $0x76543210
      14:   66 0f 6f 04 24          movdqa (%esp),%xmm0

    虽然在64位(ABI保证函数入口处的堆栈指针对齐)需要27字节:

    0000000000000000 :
       0:   48 b8 f0 de bc 9a 78 56 34 12   movabs $0x123456789abcdef0,%rax
       a:   50                              push   %rax
       b:   48 b8 10 32 54 76 98 ba dc fe   movabs $0xfedcba9876543210,%rax
      15:   50                              push   %rax
      16:   66 0f 6f 04 24                  movdqa (%rsp),%xmm0

    如果您将其中任何一个与MOVLHPS版本进行比较,您会发现它是最长的:

    0000000000000000 :
       0:   48 b8 f0 de bc 9a 78 56 34 12   movabs $0x123456789abcdef0,%rax
       a:   66 48 0f 6e c0                  movq   %rax,%xmm0
       f:   48 b8 10 32 54 76 98 ba dc fe   movabs $0xfedcba9876543210,%rax
      19:   66 48 0f 6e c8                  movq   %rax,%xmm1
      1e:   0f 16 c1                        movlhps %xmm1,%xmm0

    33字节。

    直接从指令内存加载的另一个好处是movdqa不依赖于之前的任何内容。最有可能的是,@ Paul R给出的第一个版本是你能获得的最快版本。

答案 4 :(得分:5)

最好的解决方案(特别是如果你想坚持SSE2 - 即避免使用AVX)用你的立即值的两个64位半来初始化两个寄存器(比如xmm0和xmm1),做MOVLHPS xmm0,xmm1 为了初始化64位值,最简单的解决方案是使用通用寄存器(例如,AX),然后使用MOVQ将其值传输到XMM寄存器。 所以序列将是这样的:

MOV RAX, <first_half>
MOVQ XMM0, RAX
MOV RAX, <second_half>
MOVQ XMM1, RAX
MOVLHPS XMM0,XMM1