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

Vir*_*gil 24 x86 assembly sse simd

关于这一点已经存在一个问题,但它被关闭为"含糊不清",所以我开了一个新的 - 我找到了答案,也许它也会帮助其他人.

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

Nor*_* P. 18

只是想补充一点,可以在Agner Fog的手册" 汇编语言中的优化子程序,生成常​​量",第13.4节,第121页中使用汇编来阅读有关生成各种常量的内容.


Pau*_*l R 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
Run Code Online (Sandbox Code Playgroud)

加载数据加载通常比将其嵌入指令流更好,特别是因为它需要多少指令.这是CPU执行的几个额外的uop,用于一个任意常量,这个常量不能从具有几个移位的所有常量生成.

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

如果常量的两半都相同,则可以使用SSE3进行广播加载
movddup (m64), %xmm0.

  • @Virgil:gcc工具链的不同版本在这方面有点不一致,但通常`.align`指令采用2参数的幂,所以`.align 4`表示对齐2 ^ 4 = 16字节的倍数. (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
Run Code Online (Sandbox Code Playgroud)


Vir*_*gil 6

最好的解决方案(特别是如果你想坚持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
Run Code Online (Sandbox Code Playgroud)


Fra*_*kH. 6

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

  1. 通过使用立即操作数
  2. 通过从PC相关地址加载

因此,虽然没有办法立即加载XMM寄存器中,但可以从" 紧接着 "代码执行的值存储的值进行PC相对加载(64位).这创造了类似的东西:

.align 4
.val:
    .long   0x12345678
    .long   0x9abcdef0
    .long   0xfedbca98
    .long   0x76543210
func:
     movdqa .val(%rip), %xmm0
Run Code Online (Sandbox Code Playgroud)

当你拆卸:

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字节.

其他选项是在堆栈上构造值并再次从那里加载它.在没有%rip-relative内存访问的32位x86中,仍然可以在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给出的第一个版本是你能获得的最快版本.