如何在 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 寄存器中旋转压缩四字?的主要内容,如果未能解决你的问题,请参考以下文章

将浮点数从高 xmm 四字移动到低 xmm 四字

如何将一个 XMM 128 位寄存器拆分为两个 64 位整数寄存器?

将四字移动到 xmm

将XMM寄存器推入堆栈

如何将浮点常量值移动到 xmm 寄存器中?

如何将 XMM 寄存器中的数字存储到 asm 循环中的 char 数组中 -