如何在 xmm 寄存器中旋转压缩四字?

Posted

技术标签:

【中文标题】如何在 xmm 寄存器中旋转压缩四字?【英文标题】:How to rotate packed quadwords in xmm register? 【发布时间】:2018-12-06 02:15:20 【问题描述】:

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

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

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

prorqw xmm0, 32   // rotate right packed quadwords

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

我知道 SSE2 提供:

PSHUFW随机压缩单词(16位) PSHUFD随机压缩 双字(32 位)

虽然我不知道指令是做什么的,也没有 quadword(64 位)版本。

奖金问题

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

将压缩后的双字右移 16 位:

     ╭──────────┬──────────┬──────────┬──────────╮
xmm0 │ ffeeddcc │ bbaa9988 │ 77665544 │ 33221100 │
     ╰──────────┴──────────┴──────────┴──────────╯
                        ⇓
     ╭──────────┬──────────┬──────────┬──────────╮
xmm0 │ ddccffee │ 9988bbaa │ 55447766 │ 11003322 │
     ╰──────────┴──────────┴──────────┴──────────╯

将压缩后的单词向右旋转 8 位:

     ╭──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────╮
xmm0 │ ffee │ ddcc │ bbaa │ 9988 │ 7766 │ 5544 │ 3322 │ 1100 │
     ╰──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────╯
                        ⇓
     ╭──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────╮
xmm0 │ eeff │ ccdd │ aabb │ 8899 │ 6677 │ 4455 │ 2233 │ 0011 │
     ╰──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────╯

额外奖励问题

如果是 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
     ╰──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────╯

阅读奖励

Intel Intrinsics Guide X86 Opcode and Instruction Reference Home Rotation or Shifting with x86/x64 Assembly PSHUFW explanation?

【问题讨论】:

【参考方案1】:

如果旋转计数是 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 的函数版本来说还不错,但是对于低/高 qword,您需要单独的洗牌。 (一个 imm8 控制字节仅包含四个 2 位字段。)或者使用 AVX2,用于每个通道内的奇数/偶数 qwords。


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


否则只有 SSE2 且计数不是 16 的倍数,您需要 left+right shift + OR 才能在 asm 中实际实现将 C 中的旋转表示为 @ 987654338@。 (Best practices for circular shift (rotate) operations in C++ 指出了从超出范围的移位计数中解决潜在 C UB 的方法,这对于内在函数或 asm 不是问题,因为 asm 和内在函数的行为由英特尔明确定义:SIMD 移位使移位饱和计数,而不是像标量移位那样掩盖它。)

SSE2 的移位粒度小至 16 位,因此您可以直接执行此操作。

对于字节粒度,您需要额外的掩码以将在字中的字节之间移动的位清零。 Efficient way of rotating a byte inside an AVX register。或者使用 pmullw 之类的技巧和 2 次幂元素的向量,允许每个元素的可变计数。 (AVX2 通常只有 dword/qword 的可变计数移位)。

【讨论】:

如何使用pshufdxmm0中的两个四字旋转32位? @IanBoyd:你交换每个 qword 的 32 位一半。就像 _MM_SHUFFLE(2,3, 0,1) 和内在函数一样。或者直接在 asm 中,pshufd xmm0, xmm0, 0b10_11_00_01(您可能必须删除我在位对之间使用的_ 分隔符,除非您的汇编支持 C++11 样式分隔符语法)。【参考方案2】:

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

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

pshufd:随机压缩双字

指令的编码比较棘手,Intel 尽力做到obfuscate the documentation。这个想法是您可以将 128 位 xmm 视为 32 位 双字,并将它们推送到您喜欢的任何位置:

编码很棘手:

pshufd xmm0, xmm0, 0x02030001

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

02030001

这些是从左到右排列的,告诉你应该将 32 位双字打乱到哪里的索引:

如果您正在旋转 64 位四字,它们被打包到 xmm 寄存器中,正好 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 //shuffle 打包字节

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

向右旋转(1)

是的,稍后。

脚注

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

【讨论】:

您链接到的“混淆文档”是英特尔的 intrinsics 指南。它适用于使用 C 或 C++ 编写的具有内在函数的人。对于 4x 2 位字段,您始终可以使用 _MM_SHUFFLE 宏。但如果你直接用 asm 编写,你应该查阅 Intel 的 vol.2 指令集参考手册,或者像 felixcloutier.com/x86/PSHUFD.html 这样的 HTML 摘录。 OPERATION 部分使用不同的伪代码来描述它,就班次而言。但它有一个 256 位 vpshufb 的图表示例。 (我为我提到的insns添加了指向我的答案的链接。) 顺便说一句,0x02030001 是一个用十六进制编写的 32 位常量。您需要一个 8 位常量,例如 0xb10b10110001。这 4 个块是立即字节内的 2 位字段。一旦您理解了它,英特尔的位范围表示法就非常好,并且清楚地描述了准确指令的作用。没有 SSE/AVX insn 采用 32 位立即数,但如果他们这样做,将有足够的空间来编码覆盖整个寄存器的 16 位粒度洗牌。 (log2(8) * 8 = 24 bits 用于 8 x 3 位字段。或者他们更有可能使用 4 位字段,高位可选为零)。 @PeterCordes 我明天必须回来修复图像。希望到那时我能得到关于如何 ROR 的答案。

以上是关于如何在 xmm 寄存器中旋转压缩四字?的主要内容,如果未能解决你的问题,请参考以下文章