如何在 xmm 寄存器中旋转打包四字?

How to rotate packed quadwords in xmm register?

给定一个包含两个四字(即两个 64 位整数)的 128 位 xmm 寄存器:

     ╭──────────────────┬──────────────────╮
xmm0 │ ffeeddccbbaa9988 │ 7766554433221100 │
     ╰──────────────────┴──────────────────╯

如何对单个四字执行旋转?例如:

prorqw xmm0, 32   // rotate right packed quadwords

     ╭──────────────────┬──────────────────╮
xmm0 │ bbaa9988ffeeddcc │ 3322110077665544 │
     ╰──────────────────┴──────────────────╯

我知道 SSE2 提供:

虽然我不知道指令的作用,也没有 quadword(64 位)版本。

奖金问题

您将如何执行 xmm 寄存器的 ROR - 假设打包数据为 other 大小?

加分题

如果是 256 位 ymm 寄存器,您将如何执行上述操作?

     ╭──────────────────────────────────┬──────────────────────────────────╮
ymm0 │ 2f2e2d2c2b2a29282726252423222120 │ ffeeddccbbaa99887766554433221100 │ packed doublequadwords
     ╰──────────────────────────────────┴──────────────────────────────────╯
     ╭──────────────────┬──────────────────┬──────────────────┬──────────────────╮
ymm0 │ 2f2e2d2c2b2a2928 │ 2726252423222120 │ ffeeddccbbaa9988 │ 7766554433221100 │ packed quadwords
     ╰──────────────────┴──────────────────┴──────────────────┴──────────────────╯
     ╭──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────╮
ymm0 │ 2f2e2d2c │ 2b2a2928 │ 27262524 │ 23222120 │ ffeeddcc │ bbaa9988 │ 77665544 │ 33221100 │ packed doublewords
     ╰──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────╯
     ╭──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────╮
ymm0 │ 2f2e │ 2d2c │ 2b2a │ 2928 │ 2726 │ 2524 │ 2322 │ 2120 │ ffee │ ddcc │ bbaa │ 9988 │ 7766 │ 5544 │ 3322 │ 1100 │ packed words
     ╰──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────╯

红利阅读

如果旋转计数是 8 的倍数,则可以使用字节随机播放。 SSSE3 pshufb 使用控制掩码可以在一条指令中处理任何其他 8 的倍数。

SSE2 pshufd 可以处理 count=32,交换每个 qword 的两半:_MM_SHUFFLE(2,3, 0,1),或者在 asm pshufd xmm0, xmm0, 0b10_11_00_01(NASM 支持 _作为可选的分隔符,如 C++11 中的数字文字。)

SSE2 pshuflw + pshufhw 对于 16 的倍数计数对于没有 SSSE3 的函数版本来说还不错,但是您需要为 low/high qword 单独洗牌。 (一个 imm8 控制字节只包含四个 2 位字段。)或者对于 AVX2,每个通道中的 odd/even 个 qwords。


如果旋转计数不是8的倍数,则有AVX512Fvprolq zmm0, zmm1, 13vprorq。也有可变计数版本,每个元素计数来自另一个向量而不是立即数。 vprolvq / vprorvq。也可用于双字粒度,但不能用于字或字节。


否则,只有 SSE2 和一个不是 16 的倍数的计数,您需要左移+右移 + OR 才能在 asm 中实际实现在 C 中表达旋转的常用方式作为 (x << n) | (x >> (64-n))。 (Best practices for circular shift (rotate) operations in C++ 指出了解决超出范围的移位计数的潜在 C UB 的方法,这不是内在函数或 asm 的问题,因为 asm 和内在函数的行为由英特尔明确定义:SIMD shifts saturate移位计数,而不是像标量移位那样屏蔽它。)

SSE2 的移位粒度小至 16 位,因此您可以直接这样做。

对于字节粒度,您需要额外的屏蔽来将字中字节之间移动的位清零。 。或者使用 pmullw 之类的技巧和 2 的幂元素向量,允许每个元素的可变计数。 (其中 AVX2 通常只有 dword/qword 的可变计数移位)。

虽然我询问了执行向右旋转,但 ROR 的一个子集是当您对两个 64 位值执行恰好 32 位的 ROR 时。这使得你的任意 rotate 变成高低 32 位的简单交换:

知道您只是在执行 32 位(即 双字)交换,您可以使用另一条指令:

  • pshufd:随机打包双字

指令的编码很棘手,英特尔尽最大努力obfuscate the documentation。这个想法是,您可以将 128 位 xmm 视为 32 位 doublewords,并将它们推送到您喜欢的任何位置:

编码很棘手:

pshufd xmm0, xmm0, 0x02030001

因为我正在推动 四个 双字,掩码由四个块组成:

02 03 00 01

这些是从左到右排列的,告诉您应该将 32 位双字改组到何处的索引:

如果您要将打包到 xmm 寄存器中的 64 位四字按正好 32 位旋转,您可以使用:

pshufd xmm0, xmm0, 0x02030001 //rotate packed quadwords by 32-bits¹

向右旋转(16)

现在如果:

  • 而不是打包到 xmm
  • 中的 64 位四字的 ROR(32)
  • 我想ROR(16)

我们可以应用相同的技巧。假设将64位四字分成16位字,打乱:

pshufw xmm0, xmm0, 0x0605040702010003 //shuffle packed words¹

除了 pshufw 不能对 xmm 寄存器进行操作。所以我说服自己停了下来。

向右旋转(24)

现在如果:

  • 而不是打包到 xmm
  • 中的 64 位四字的 ROR(32)
  • 我想ROR(24)

我们可以应用相同的东西。假设将 64 位四字分成 8 位字....

pshufb xmm0, xmm0, something //打乱打包字节

好吧,我明天去拿这个。现在我累了。我希望只输入一行代码;取而代之的是四个小时的痛苦。我只是假设人们现在已经记录了所有这些基本操作; CPU 已经存在至少 3 年了。

向右旋转(1)

是的,稍后。

脚注

¹我想。我不确定我的编码是否正确。