将两个QWORD从通用寄存器移动到XMM寄存器作为高位/低位。

3

在与masm for ml64一起使用时,我试图将两个无符号qword从r9和r10移动到xmm0作为一个无符号128b int。

到目前为止,我想到了以下代码:

mov r9, 111             ;low qword for test
mov r10, 222            ;high qword for test

movq xmm0, r9           ;move low to xmm0 lower bits
movq xmm1, r10          ;move high to xmm1 lower bits
pslldq xmm1, 4          ;shift xmm1 lower half to higher half   
por xmm0, xmm1          ;or the 2 halves together

我认为它有效的原因是:
movq rax, xmm0

返回正确的低值

psrldq xmm0, 4
movq rax, xmm0

返回正确的高值。

问题是,有没有更好的方法?我正在浏览英特尔指令集指南,但我不太擅长猜测可能存在的指令名称。


@Johan,那是反向和有符号的,完全不同。 - user81993
3
PINSRQ 是什么意思? - Jester
请看我在此gcc错误报告中对各种CPU的优点的讨论:_mm_set_epi64x不应存储/重新加载以用于-mtune=haswell,Zen应避免存储/重新加载,通用应考虑此问题。另外相关:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80833 - Peter Cordes
2个回答

2
你的字节移位/OR 函数有问题,因为你只移动了4个字节而不是8个;当你的8字节qword测试值在其上半部分没有设置任何位时,它可能会工作。
SSE/AVX SIMD指令集包括一个您可以用于此操作的解包指令
mov r9, 111         ; test input: low half
mov r10, 222        ; test input: high half

vmovq xmm0, r9      ; move 64 bit wide general purpose register into lower xmm half
vmovq xmm1, r10     ; ditto

vpunpcklqdq xmm0, xmm0, xmm1    ; i.e. xmm0 = low(xmm1) low(xmm0)

这意味着vpunpcklqdq指令将每个低位源四字(64位)解压缩(或交错)成双四字(即完整的XMM寄存器宽度)。

与您原始的代码片段相比,您可以节省一条指令。

(我使用了VEX AVX助记符。如果您想针对SSE2,则必须删除v前缀。)


或者,您可以使用插入指令将第二个值移动到上半部分:
mov r9, 111         ; test input
mov r10, 222        ; test input

vmovq xmm0, r9      ; move 64 bit wide general purpose register into lower xmm half

vpinsrq xmm0, xmm0, r10, 1    ; i.e. xmm0 = r9 low(ymm0)

就执行而言,在微操作级别上,这并没有太大的区别,即 vpinsrqvmov + vpunpcklqdq 的“代价”相同,但它可以编码成更短的代码。
非 AVX 版本需要 SSE4.1 来支持 pinsrq

如果您拥有AVX,那就意味着您具有[v]pinsrq(非VEX编码的AVX或SSE4.1),在大多数CPU上,在大多数情况下,它比较优秀(相等数量的uops,更小的代码大小;大多数支持AVX的CPU通过uop缓存相当有效地处理多uop指令)。此外,如果您拥有AVX,您应该使用vmovq。(虽然如果上半部分是干净的,则自由混合SSE和128位AVX指令是可以的)。因此,基本上这个答案只对非AVX情况有意义。 - Peter Cordes
@PeterCordes,是的,vmovq 在这里确实有意义(我更新了我的答案)。根据 https://uops.info/table.html,与 vpunpcklqdq 相比,vpinsrq 的延迟可能更高,并且它具有两倍的 uops 数量、端口使用和吞吐量。(在 Skylake 上) - maxschlepzig
是的,vpinsrq 解码后基本上与 vmovq + vpunpcklqdq 相同。就像我试图说的那样,在大多数 CPU 上,它只是更紧凑的机器码,用于执行相同的 uops(从整数到 SIMD-整数域获取数据的 p5,以及将其与另一个寄存器中的数据混洗/混合的 p5)。不幸的是,即使在具有 GP -> SIMD vpbroadcastq 的 SKX 上,它也无法解码为广播复制和 p015 立即混合 :/ - Peter Cordes
所以,如果你单独使用xmm1中的r10没有用处,那么最好使用vpinsrq。在前端,可能存在两个单uop指令更适合解码和/或uop缓存的情况(例如需要“复杂”解码器,或者不适合在uop缓存行中留下一个插槽),但在后端它是等效的。 - Peter Cordes

0

借助您的堆栈,我们可以轻松解决问题:

    push   r10
    push   r9
ifdef ALIGNED
    movdqa xmm0, xmmword ptr [esp]
else
    movdqu xmm0, xmmword ptr [esp]
endif
    add    esp, 16

如果你的 __uint128 恰好存在于堆栈上,只需去除多余的指令。

1
这确实会遇到存储转发停顿的问题。 - harold
对于延迟来说不好,与ALU相比的吞吐量达到盈亏平衡,除非周围的代码在ALU uops上受到瓶颈的限制。请参阅我在此gcc错误报告中讨论的各种CPU的优点:_mm_set_epi64x shouldn't store/reload for -mtune=haswell, Zen should avoid store/reload, and generic should think about it。同时相关的链接:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80833 - Peter Cordes
2
此外,在64位模式下,请使用[rsp]而不是[esp]。无法仅使用rsp的低32位使push/pop正常工作,因此即使在像Linux x32这样的ILP32 ABI中,您也可以始终安全地假定esp被零扩展为64位,并通过使用64位寻址模式避免地址大小前缀。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接