将 _mm_clmulepi64_si128 转换为 vmull_{high}_p64
Convert _mm_clmulepi64_si128 to vmull_{high}_p64
我有以下 Intel PCLMULQDQ intrinsic(无进位乘法):
__m128i a, b; // Set to some value
__m128i r = _mm_clmulepi64_si128(a, b, 0x10);
0x10
告诉我乘法是:
r = a[63:0] * b[127:64]
我需要将其转换为 NEON(或更准确地说,使用 Crypto 扩展):
poly64_t a, b; // Set to some value
poly16x8_t = vmull_p64(...) or vmull_high_p64(...);
我认为 vmull_p64
适用于低 64 位,而 vmull_high_p64
适用于高 64 位。我 认为 我需要将其中一个值移动 128 位值以模仿 _mm_clmulepi64_si128(a, b, 0x10)
。 PMULL, PMULL2 (vector) are not too clear, and I'm not sure what the result will be because I don't understand 2's arrangement specifier. The ARM ACLE 2.0 的文档也不太有用:
poly128_t vmull_p64 (poly64_t, poly64_t);
Performs widening polynomial multiplication on double-words low part.
Available on ARMv8 AArch32 and AArch64.
poly128_t vmull_high_p64 (poly64x2_t, poly64x2_t);
Performs widening polynomial multiplication on double-words high part.
Available on ARMv8 AArch32 and AArch64.
如何将 _mm_clmulepi64_si128
转换为 vmull_{high}_p64
?
对于考虑投资 NEON、PMULL 和 PMULL2 的任何人...64 位乘法器和多项式支持是值得的。基准显示 GMAC 的 GCC 代码从 12.7 cpb 和 90 MB/s (C/C++) 下降到 1.6 cpb 和 670 MB/s(NEON 和 PMULL{2})。
由于您通过评论澄清了混淆的根源:
完全乘法产生的结果是输入的两倍宽。一个 add 最多可以产生一个进位位,但是一个 mul 产生整个上半部分。
乘法完全等同于移位 + 加法,这些移位使一个操作数的位高达 2N - 1(当输入为 N 位宽时)。参见 Wikipedia's example。
在像x86's mul
instruction这样的普通整数乘法中(在加法步骤中有进位),部分和的进位可以设置高位,所以结果恰好是两倍宽。
XOR 是不带进位的加法,因此无进位乘法是相同的移位加法算法,但使用 XOR 而不是带进位加法。在无进位乘法中,没有进位,因此全角结果的最高位始终为零。英特尔甚至在 x86 insn ref 手册的操作部分明确说明了 pclmuludq
:DEST[127] ← 0;
。该部分准确记录了产生结果的所有移位和异或运算。
PMULL[2]
文档对我来说似乎很清楚。目标必须是一个 .8H
向量(这意味着八个 16 位(半字)元素)。 PMULL
的源必须是 .8B
向量(8 个单字节元素),而 PMULL2
的源必须是 .16B
(16 个单字节元素,仅使用每个来源的前 8 位)。
如果这是 ARM32 NEON,其中每个 16B 向量寄存器的上半部分是一个奇数较窄的寄存器,PMULL2
将没有任何用处。
虽然没有 "operation" 部分来描述 确切地 哪些位与哪些其他位相乘。幸好有paper linked in comments nicely summarizes the available instructions for ARMv7, and ARMv8 32 and 64 bit. The .8B / .8H organization specifiers seem to be bogus, because PMULL
does perform a single 64x64 -> 128 carryless mul like SSE's pclmul指令。 ARMv7 VMULL.P8
NEON insn 确实做了一个打包的 8x8->16,但清楚地表明 PMULL
(和 ARMv8 AArch32 VMULL.P8
)是不同的。
很遗憾 ARM 文档没有说明这些;它似乎非常缺乏,尤其是。重新误导 .8B
矢量组织的东西。该论文显示了一个使用预期的 .1q
和 .1d
(以及 .2d
)组织的示例,因此汇编器可能不关心您认为数据的含义,只要它是正确的尺寸。
要进行高低乘法运算,您需要移动其中一个。
例如,如果您需要所有四种组合(a0*b0、a1*b0、a0*b1、a1*b1),就像构建 128x128 - > 64x64 中的 128 次乘法 -> 128 次乘法(使用 Karatsuba),你可以这样做:
pmull a0b0.8H, a.8B, b.8B
pmull2 a1b1.8H, a.16B, b.16B
swap a's top and bottom half, which I assume can be done efficiently somehow
pmull a1b0.8H, swapped_a.8B, b.8B
pmull2 a0b1.8H, swapped_a.16B, b.16B
所以看起来 ARM 的设计选择包括 lower-lower 和 upper-upper,但不包括交叉乘法指令(或像 x86 那样的选择器常量)不会导致太多效率低下。由于 ARM 指令不能像 x86 的可变长度机器编码那样只添加额外的立即数,所以这可能不是一个选择。
同一事物的另一个版本,带有真正的随机播放指令和之后的 Karatsuba(从 Implementing GCM on ARMv8 逐字复制)。但仍然是虚构的注册名称。该论文沿途重复使用了相同的临时寄存器,但我已经按照我可能为 C 内部函数版本命名的方式命名它们。这使得扩展精度乘法的操作非常清楚。编译器可以为我们重用死寄存器。
1: pmull a0b0.1q, a.1d, b.1d
2: pmull2 a1b1.1q, a.2d, b.2d
3: ext.16b swapped_b, b, b, #8
4: pmull a0b1.1q, a.1d, swapped_b.1d
5: pmull2 a1b0.1q, a.2d, swapped_b.2d
6: eor.16b xor_cross_muls, a0b1, a1b0
7: ext.16b cross_low, zero, xor_cross_muls, #8
8: eor.16b result_low, a0b0, cross_low
9: ext.16b cross_high, xor_cross_muls, zero, #8
10: eor.16b result_high, a1b1, cross_high
How do I convert _mm_clmulepi64_si128 to vmull_{high}_p64?
下面是示例程序的结果。转化次数为:
_mm_clmulepi64_si128(a, b, 0x00)
→vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b, 0))
_mm_clmulepi64_si128(a, b, 0x01)
→vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b, 0))
_mm_clmulepi64_si128(a, b, 0x10)
→vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b, 1))
_mm_clmulepi64_si128(a, b, 0x11)
→vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b, 1))
对于情况(4),_mm_clmulepi64_si128(a, b, 0x11)
,以下也成立:
_mm_clmulepi64_si128(a, b, 0x11)
→vmull_high_p64((poly64x2_t)a, (poly64x2_t)b)
我猜如果不小心,情况 (1) 到 (4) 可能会溢出到内存中,因为 vgetq_lane_u64
returns 是标量或非向量类型。我还猜测情况 (5) 有留在 Q 寄存器中的倾向,因为它是向量类型。
x86_64 和 _mm_clmulepi64_si128:
$ ./mul-sse-neon.exe
IS_X86: true
****************************************
clmulepi64(a, b, 0x00)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0x606060606060606, r[1]: 0x606060606060606
****************************************
clmulepi64(a, b, 0x01)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0xc0c0c0c0c0c0c0c, r[1]: 0xc0c0c0c0c0c0c0c
****************************************
clmulepi64(a, b, 0x10)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0xa0a0a0a0a0a0a0a, r[1]: 0xa0a0a0a0a0a0a0a
****************************************
clmulepi64(a, b, 0x11)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0x1414141414141414, r[1]: 0x1414141414141414
ARM64 和 vmull_p64:
$ ./mul-sse-neon.exe
IS_ARM: true
****************************************
vmull_p64(a, b, 0x00)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0x606060606060606, r[1]: 0x606060606060606
****************************************
vmull_p64(a, b, 0x01)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0xa0a0a0a0a0a0a0a, r[1]: 0xa0a0a0a0a0a0a0a
****************************************
vmull_p64(a, b, 0x10)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0xc0c0c0c0c0c0c0c, r[1]: 0xc0c0c0c0c0c0c0c
****************************************
vmull_p64(a, b, 0x11)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0x1414141414141414, r[1]: 0x1414141414141414
示例程序mul-sse-neon.cc:
#define IS_ARM (__arm__ || __arm32__ || __aarch32__ || __arm64__ || __aarch64__)
#define IS_X86 (__i386__ || __i586__ || __i686__ || __amd64__ || __x86_64__)
#if (IS_ARM)
# include <arm_neon.h>
# if defined(__ARM_ACLE) || defined(__GNUC__)
# include <arm_acle.h>
# endif
#endif
#if (IS_X86)
# include <emmintrin.h>
# if defined(__GNUC__)
# include <x86intrin.h>
# endif
#endif
#if (IS_ARM)
typedef uint64x2_t word128;
#elif (IS_X86)
typedef __m128i word128;
#else
# error "Need a word128"
#endif
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
void print_val(const word128* value, const char* label);
/* gcc -DNDEBUG -g3 -O0 -march=native mul-sse-neon.cc -o mul-sse-neon.exe */
/* gcc -DNDEBUG -g3 -O0 -march=armv8-a+crc+crypto mul-sse-neon.cc -o mul-sse-neon.exe */
int main(int argc, char* argv[])
{
#if (IS_ARM)
printf("IS_ARM: true\n");
#elif (IS_X86)
printf("IS_X86: true\n");
#endif
word128 a,b, r;
a[0] = 0x2222222222222222, a[1] = 0x4444444444444444;
b[0] = 0x3333333333333333, b[1] = 0x5555555555555555;
#if (IS_ARM)
printf("****************************************\n");
printf("vmull_p64(a, b, 0x00)\n");
r = (uint64x2_t)vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b,0));
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("vmull_p64(a, b, 0x01)\n");
r = (uint64x2_t)vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b,1));
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("vmull_p64(a, b, 0x10)\n");
r = (uint64x2_t)vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b,0));
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("vmull_p64(a, b, 0x11)\n");
r = (uint64x2_t)vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b,1));
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
#elif (IS_X86)
printf("****************************************\n");
printf("clmulepi64(a, b, 0x00)\n");
r = _mm_clmulepi64_si128(a, b, 0x00);
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("clmulepi64(a, b, 0x01)\n");
r = _mm_clmulepi64_si128(a, b, 0x01);
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("clmulepi64(a, b, 0x10)\n");
r = _mm_clmulepi64_si128(a, b, 0x10);
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("clmulepi64(a, b, 0x11)\n");
r = _mm_clmulepi64_si128(a, b, 0x11);
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
#endif
return 0;
}
static const word128 s_v = {0,0};
static const char s_l[] = "";
void print_val(const word128* value, const char* label)
{
const word128* v = (value ? value : &s_v);
const char* l = (label ? label : s_l);
#if (IS_ARM)
printf("%s[0]: 0x%" PRIx64 ", %s[1]: 0x%" PRIx64 "\n", l, (*v)[0], l, (*v)[1]);
#elif (IS_X86)
printf("%s[0]: 0x%" PRIx64 ", %s[1]: 0x%" PRIx64 "\n", l, (*v)[0], l, (*v)[1]);
#endif
}
vmull_high_p64
的代码如下。它总是产生相同的结果,因为它总是采用相同的高字:
printf("****************************************\n");
printf("vmull_p64(a, b)\n");
r = (uint64x2_t)vmull_high_p64((poly64x2_t)a, (poly64x2_t)b);
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
为了完整起见,将数据切换为:
word128 a,b, r;
a[0] = 0x2222222233333333, a[1] = 0x4444444455555555;
b[0] = 0x6666666677777777, b[1] = 0x8888888899999999;
产生以下结果:
$ ./mul-sse-neon.exe
IS_X86: true
****************************************
clmulepi64(a, b, 0x00)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0xd0d0d0d09090909, r[1]: 0xc0c0c0c08080808
****************************************
clmulepi64(a, b, 0x01)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x191919191b1b1b1b, r[1]: 0x181818181a1a1a1a
****************************************
clmulepi64(a, b, 0x10)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x111111111b1b1b1b, r[1]: 0x101010101a1a1a1a
****************************************
clmulepi64(a, b, 0x11)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x212121212d2d2d2d, r[1]: 0x202020202c2c2c2c
并且:
$ ./mul-sse-neon.exe
IS_ARM: true
****************************************
vmull_p64(a, b, 0x00)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0xd0d0d0d09090909, r[1]: 0xc0c0c0c08080808
****************************************
vmull_p64(a, b, 0x01)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x111111111b1b1b1b, r[1]: 0x101010101a1a1a1a
****************************************
vmull_p64(a, b, 0x10)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x191919191b1b1b1b, r[1]: 0x181818181a1a1a1a
****************************************
vmull_p64(a, b, 0x11)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x212121212d2d2d2d, r[1]: 0x202020202c2c2c2c
我有以下 Intel PCLMULQDQ intrinsic(无进位乘法):
__m128i a, b; // Set to some value
__m128i r = _mm_clmulepi64_si128(a, b, 0x10);
0x10
告诉我乘法是:
r = a[63:0] * b[127:64]
我需要将其转换为 NEON(或更准确地说,使用 Crypto 扩展):
poly64_t a, b; // Set to some value
poly16x8_t = vmull_p64(...) or vmull_high_p64(...);
我认为 vmull_p64
适用于低 64 位,而 vmull_high_p64
适用于高 64 位。我 认为 我需要将其中一个值移动 128 位值以模仿 _mm_clmulepi64_si128(a, b, 0x10)
。 PMULL, PMULL2 (vector) are not too clear, and I'm not sure what the result will be because I don't understand 2's arrangement specifier. The ARM ACLE 2.0 的文档也不太有用:
poly128_t vmull_p64 (poly64_t, poly64_t);
Performs widening polynomial multiplication on double-words low part. Available on ARMv8 AArch32 and AArch64.
poly128_t vmull_high_p64 (poly64x2_t, poly64x2_t);
Performs widening polynomial multiplication on double-words high part. Available on ARMv8 AArch32 and AArch64.
如何将 _mm_clmulepi64_si128
转换为 vmull_{high}_p64
?
对于考虑投资 NEON、PMULL 和 PMULL2 的任何人...64 位乘法器和多项式支持是值得的。基准显示 GMAC 的 GCC 代码从 12.7 cpb 和 90 MB/s (C/C++) 下降到 1.6 cpb 和 670 MB/s(NEON 和 PMULL{2})。
由于您通过评论澄清了混淆的根源:
完全乘法产生的结果是输入的两倍宽。一个 add 最多可以产生一个进位位,但是一个 mul 产生整个上半部分。
乘法完全等同于移位 + 加法,这些移位使一个操作数的位高达 2N - 1(当输入为 N 位宽时)。参见 Wikipedia's example。
在像x86's mul
instruction这样的普通整数乘法中(在加法步骤中有进位),部分和的进位可以设置高位,所以结果恰好是两倍宽。
XOR 是不带进位的加法,因此无进位乘法是相同的移位加法算法,但使用 XOR 而不是带进位加法。在无进位乘法中,没有进位,因此全角结果的最高位始终为零。英特尔甚至在 x86 insn ref 手册的操作部分明确说明了 pclmuludq
:DEST[127] ← 0;
。该部分准确记录了产生结果的所有移位和异或运算。
PMULL[2]
文档对我来说似乎很清楚。目标必须是一个 .8H
向量(这意味着八个 16 位(半字)元素)。 PMULL
的源必须是 .8B
向量(8 个单字节元素),而 PMULL2
的源必须是 .16B
(16 个单字节元素,仅使用每个来源的前 8 位)。
如果这是 ARM32 NEON,其中每个 16B 向量寄存器的上半部分是一个奇数较窄的寄存器,PMULL2
将没有任何用处。
虽然没有 "operation" 部分来描述 确切地 哪些位与哪些其他位相乘。幸好有paper linked in comments nicely summarizes the available instructions for ARMv7, and ARMv8 32 and 64 bit. The .8B / .8H organization specifiers seem to be bogus, because PMULL
does perform a single 64x64 -> 128 carryless mul like SSE's pclmul指令。 ARMv7 VMULL.P8
NEON insn 确实做了一个打包的 8x8->16,但清楚地表明 PMULL
(和 ARMv8 AArch32 VMULL.P8
)是不同的。
很遗憾 ARM 文档没有说明这些;它似乎非常缺乏,尤其是。重新误导 .8B
矢量组织的东西。该论文显示了一个使用预期的 .1q
和 .1d
(以及 .2d
)组织的示例,因此汇编器可能不关心您认为数据的含义,只要它是正确的尺寸。
要进行高低乘法运算,您需要移动其中一个。
例如,如果您需要所有四种组合(a0*b0、a1*b0、a0*b1、a1*b1),就像构建 128x128 - > 64x64 中的 128 次乘法 -> 128 次乘法(使用 Karatsuba),你可以这样做:
pmull a0b0.8H, a.8B, b.8B
pmull2 a1b1.8H, a.16B, b.16B
swap a's top and bottom half, which I assume can be done efficiently somehow
pmull a1b0.8H, swapped_a.8B, b.8B
pmull2 a0b1.8H, swapped_a.16B, b.16B
所以看起来 ARM 的设计选择包括 lower-lower 和 upper-upper,但不包括交叉乘法指令(或像 x86 那样的选择器常量)不会导致太多效率低下。由于 ARM 指令不能像 x86 的可变长度机器编码那样只添加额外的立即数,所以这可能不是一个选择。
同一事物的另一个版本,带有真正的随机播放指令和之后的 Karatsuba(从 Implementing GCM on ARMv8 逐字复制)。但仍然是虚构的注册名称。该论文沿途重复使用了相同的临时寄存器,但我已经按照我可能为 C 内部函数版本命名的方式命名它们。这使得扩展精度乘法的操作非常清楚。编译器可以为我们重用死寄存器。
1: pmull a0b0.1q, a.1d, b.1d
2: pmull2 a1b1.1q, a.2d, b.2d
3: ext.16b swapped_b, b, b, #8
4: pmull a0b1.1q, a.1d, swapped_b.1d
5: pmull2 a1b0.1q, a.2d, swapped_b.2d
6: eor.16b xor_cross_muls, a0b1, a1b0
7: ext.16b cross_low, zero, xor_cross_muls, #8
8: eor.16b result_low, a0b0, cross_low
9: ext.16b cross_high, xor_cross_muls, zero, #8
10: eor.16b result_high, a1b1, cross_high
How do I convert _mm_clmulepi64_si128 to vmull_{high}_p64?
下面是示例程序的结果。转化次数为:
_mm_clmulepi64_si128(a, b, 0x00)
→vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b, 0))
_mm_clmulepi64_si128(a, b, 0x01)
→vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b, 0))
_mm_clmulepi64_si128(a, b, 0x10)
→vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b, 1))
_mm_clmulepi64_si128(a, b, 0x11)
→vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b, 1))
对于情况(4),_mm_clmulepi64_si128(a, b, 0x11)
,以下也成立:
_mm_clmulepi64_si128(a, b, 0x11)
→vmull_high_p64((poly64x2_t)a, (poly64x2_t)b)
我猜如果不小心,情况 (1) 到 (4) 可能会溢出到内存中,因为 vgetq_lane_u64
returns 是标量或非向量类型。我还猜测情况 (5) 有留在 Q 寄存器中的倾向,因为它是向量类型。
x86_64 和 _mm_clmulepi64_si128:
$ ./mul-sse-neon.exe
IS_X86: true
****************************************
clmulepi64(a, b, 0x00)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0x606060606060606, r[1]: 0x606060606060606
****************************************
clmulepi64(a, b, 0x01)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0xc0c0c0c0c0c0c0c, r[1]: 0xc0c0c0c0c0c0c0c
****************************************
clmulepi64(a, b, 0x10)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0xa0a0a0a0a0a0a0a, r[1]: 0xa0a0a0a0a0a0a0a
****************************************
clmulepi64(a, b, 0x11)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0x1414141414141414, r[1]: 0x1414141414141414
ARM64 和 vmull_p64:
$ ./mul-sse-neon.exe
IS_ARM: true
****************************************
vmull_p64(a, b, 0x00)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0x606060606060606, r[1]: 0x606060606060606
****************************************
vmull_p64(a, b, 0x01)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0xa0a0a0a0a0a0a0a, r[1]: 0xa0a0a0a0a0a0a0a
****************************************
vmull_p64(a, b, 0x10)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0xc0c0c0c0c0c0c0c, r[1]: 0xc0c0c0c0c0c0c0c
****************************************
vmull_p64(a, b, 0x11)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0x1414141414141414, r[1]: 0x1414141414141414
示例程序mul-sse-neon.cc:
#define IS_ARM (__arm__ || __arm32__ || __aarch32__ || __arm64__ || __aarch64__)
#define IS_X86 (__i386__ || __i586__ || __i686__ || __amd64__ || __x86_64__)
#if (IS_ARM)
# include <arm_neon.h>
# if defined(__ARM_ACLE) || defined(__GNUC__)
# include <arm_acle.h>
# endif
#endif
#if (IS_X86)
# include <emmintrin.h>
# if defined(__GNUC__)
# include <x86intrin.h>
# endif
#endif
#if (IS_ARM)
typedef uint64x2_t word128;
#elif (IS_X86)
typedef __m128i word128;
#else
# error "Need a word128"
#endif
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
void print_val(const word128* value, const char* label);
/* gcc -DNDEBUG -g3 -O0 -march=native mul-sse-neon.cc -o mul-sse-neon.exe */
/* gcc -DNDEBUG -g3 -O0 -march=armv8-a+crc+crypto mul-sse-neon.cc -o mul-sse-neon.exe */
int main(int argc, char* argv[])
{
#if (IS_ARM)
printf("IS_ARM: true\n");
#elif (IS_X86)
printf("IS_X86: true\n");
#endif
word128 a,b, r;
a[0] = 0x2222222222222222, a[1] = 0x4444444444444444;
b[0] = 0x3333333333333333, b[1] = 0x5555555555555555;
#if (IS_ARM)
printf("****************************************\n");
printf("vmull_p64(a, b, 0x00)\n");
r = (uint64x2_t)vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b,0));
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("vmull_p64(a, b, 0x01)\n");
r = (uint64x2_t)vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b,1));
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("vmull_p64(a, b, 0x10)\n");
r = (uint64x2_t)vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b,0));
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("vmull_p64(a, b, 0x11)\n");
r = (uint64x2_t)vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b,1));
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
#elif (IS_X86)
printf("****************************************\n");
printf("clmulepi64(a, b, 0x00)\n");
r = _mm_clmulepi64_si128(a, b, 0x00);
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("clmulepi64(a, b, 0x01)\n");
r = _mm_clmulepi64_si128(a, b, 0x01);
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("clmulepi64(a, b, 0x10)\n");
r = _mm_clmulepi64_si128(a, b, 0x10);
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("clmulepi64(a, b, 0x11)\n");
r = _mm_clmulepi64_si128(a, b, 0x11);
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
#endif
return 0;
}
static const word128 s_v = {0,0};
static const char s_l[] = "";
void print_val(const word128* value, const char* label)
{
const word128* v = (value ? value : &s_v);
const char* l = (label ? label : s_l);
#if (IS_ARM)
printf("%s[0]: 0x%" PRIx64 ", %s[1]: 0x%" PRIx64 "\n", l, (*v)[0], l, (*v)[1]);
#elif (IS_X86)
printf("%s[0]: 0x%" PRIx64 ", %s[1]: 0x%" PRIx64 "\n", l, (*v)[0], l, (*v)[1]);
#endif
}
vmull_high_p64
的代码如下。它总是产生相同的结果,因为它总是采用相同的高字:
printf("****************************************\n");
printf("vmull_p64(a, b)\n");
r = (uint64x2_t)vmull_high_p64((poly64x2_t)a, (poly64x2_t)b);
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
为了完整起见,将数据切换为:
word128 a,b, r;
a[0] = 0x2222222233333333, a[1] = 0x4444444455555555;
b[0] = 0x6666666677777777, b[1] = 0x8888888899999999;
产生以下结果:
$ ./mul-sse-neon.exe
IS_X86: true
****************************************
clmulepi64(a, b, 0x00)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0xd0d0d0d09090909, r[1]: 0xc0c0c0c08080808
****************************************
clmulepi64(a, b, 0x01)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x191919191b1b1b1b, r[1]: 0x181818181a1a1a1a
****************************************
clmulepi64(a, b, 0x10)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x111111111b1b1b1b, r[1]: 0x101010101a1a1a1a
****************************************
clmulepi64(a, b, 0x11)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x212121212d2d2d2d, r[1]: 0x202020202c2c2c2c
并且:
$ ./mul-sse-neon.exe
IS_ARM: true
****************************************
vmull_p64(a, b, 0x00)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0xd0d0d0d09090909, r[1]: 0xc0c0c0c08080808
****************************************
vmull_p64(a, b, 0x01)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x111111111b1b1b1b, r[1]: 0x101010101a1a1a1a
****************************************
vmull_p64(a, b, 0x10)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x191919191b1b1b1b, r[1]: 0x181818181a1a1a1a
****************************************
vmull_p64(a, b, 0x11)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x212121212d2d2d2d, r[1]: 0x202020202c2c2c2c