如何在 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, 13
和 vprorq
。也可用于可变计数版本,每个元素的计数来自另一个向量而不是立即数。 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 的可变计数移位)。
【讨论】:
如何使用pshufd
将xmm0
中的两个四字旋转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
因为我正在推动 四个 双字,所以掩码由四个块组成:
02
03
00
01
这些是从左到右排列的,告诉你应该将 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 位常量,例如 0xb1
或 0b10110001
。这 4 个块是立即字节内的 2 位字段。一旦您理解了它,英特尔的位范围表示法就非常好,并且清楚地描述了准确指令的作用。没有 SSE/AVX insn 采用 32 位立即数,但如果他们这样做,将有足够的空间来编码覆盖整个寄存器的 16 位粒度洗牌。 (log2(8) * 8 = 24 bits
用于 8 x 3 位字段。或者他们更有可能使用 4 位字段,高位可选为零)。
@PeterCordes 我明天必须回来修复图像。希望到那时我能得到关于如何 ROR 的答案。以上是关于如何在 xmm 寄存器中旋转压缩四字?的主要内容,如果未能解决你的问题,请参考以下文章