如何将AVX YMM寄存器所有值设为相同值(0/1/指定值)
我是汇编代码与SSE/AVX指令的初学者,目前需要为256位YMM寄存器的所有存储位置赋指定值,但不确定最终操作结果是否正确,相关操作过程、调试结果及待解答问题如下:
1. YMM寄存器全0/全1赋值实现
__asm__ __volatile__( "vpxor %%ymm0, %%ymm0, %%ymm0\n\t" // 所有位置0 // 二选一即可,替换上一行实现全1赋值 // "vpcmpeqb %%ymm0, %%ymm0, %%ymm0\n\t" // 所有位置1 : : :);
GDB调试输出结果如下:
// 全0赋值结果 ymm0 {v8_float = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_double = {0x0, 0x0, 0x0, 0x0}, v32_int8 = {0x0 <repeats 32 times>}, v16_int16 = {0x0 <repeats 16 times>}, v8_int32 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int64 = {0x0, 0x0, 0x0, 0x0}, v2_int128 = {0x0, 0x0}} // 全1赋值结果 ymm0 {v8_float = {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, v4_double = {0x7fffffffffffffff, 0x7fffffffffffffff, 0x7fffffffffffffff, 0x7fffffffffffffff}, v32_int8 = {0xff <repeats 32 times>}, v16_int16 = {0xffff <repeats 16 times>}, v8_int32 = {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, v4_int64 = {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff}, v2_int128 = {0xffffffffffffffffffffffffffffffff, 0xffffffffffffffffffffffffffffffff}}
2. YMM寄存器全位置赋值0xAA的实现
__asm__ __volatile__( "movq $0xaaaaaaaaaaaaaaaa, %%rcx\n" "vmovq %%rcx, %%xmm0\n" "vpbroadcastq %%xmm0, %%ymm0\n": : :);
GDB调试输出结果如下:
ymm0 {v8_float = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_double = {0x0, 0x0, 0x0, 0x0}, v32_int8 = {0xaa <repeats 32 times>}, v16_int16 = {0xaaaa <repeats 16 times>}, v8_int32 = {0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa}, v4_int64 = {0xaaaaaaaaaaaaaaaa, 0xaaaaaaaaaaaaaaaa, 0xaaaaaaaaaaaaaaaa, 0xaaaaaaaaaaaaaaaa}, v2_int128 = {0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}}
问题解答
1. GDB寄存器字段含义
YMM寄存器是256位长度的存储单元,本身不区分存储的数据类型,GDB列出的不同字段只是对同一段256位二进制数据按不同数据类型做的拆分解析,字段命名规则为v<拆分后元素个数>_<元素数据类型>:
v8_float:将256位拆分为8个32位单精度浮点数(float)v4_double:将256位拆分为4个64位双精度浮点数(double)v32_int8:将256位拆分为32个8位整数(字节)v16_int16:将256位拆分为16个16位整数v8_int32:将256位拆分为8个32位整数v4_int64:将256位拆分为4个64位整数v2_int128:将256位拆分为2个128位整数
所有字段共享寄存器内的同一份原始二进制数据,不会因为解析方式不同改变寄存器实际存储的值。
2. 赋值0xAA时浮点字段显示为0的原因
你的寄存器赋值结果完全正确,显示为0是GDB默认浮点数打印精度不足导致的:
每个32位float片段对应的值是0xaaaaaaaa,换算成十进制浮点数约为-3×10^-13,是一个绝对值极小的负数;每个64位double片段对应0xaaaaaaaaaaaaaaaa,换算后也是绝对值极小的数。GDB默认打印浮点数只显示6位小数,这类极小值会被四舍五入显示为0。如果需要查看精确值,可以在GDB中用p /f $ymm0.v8_float[0]指定打印格式查看实际数值。
另外全1赋值时v4_double显示为0x7fffffffffffffff也是GDB打印NaN值时的显示问题,整数视图显示全0xff就说明寄存器位是正确的。
3. YMM寄存器全位置赋指定值的正确方法
首先要注意:你之前写的内联汇编缺少clobber列表,没有告知编译器你修改了rcx、ymm0寄存器,会导致编译器寄存器分配出错,引发未知bug。
如果要给YMM寄存器所有位置填充同一个值(比如ASCII字符'a'对应0x61),根据填充粒度选择对应广播指令即可,以填充所有字节为0x61为例:
__asm__ __volatile__( "movb $0x61, %%al\n\t" // 把目标值放到通用寄存器低8位 "vmovd %%eax, %%xmm0\n\t" // 将值写入XMM寄存器低32位,高位自动清零 "vpbroadcastb %%xmm0, %%ymm0\n\t" // 将低8位的值广播到整个YMM寄存器的32个字节 :::"rax", "ymm0", "cc" // 必须加clobber列表,声明修改了哪些寄存器 );
如果要填充的是16位/32位/64位的重复模式,只需要把广播指令换成vpbroadcastw/vpbroadcastd/vpbroadcastq即可。
更简单安全的方式是直接用编译器提供的AVX intrinsics,不用手写内联汇编,编译器会自动生成最优指令,还会自动处理寄存器分配:
#include <immintrin.h> // 所有字节填充'a' __m256i v = _mm256_set1_epi8('a'); // 所有32位int填充0x12345678 __m256i v2 = _mm256_set1_epi32(0x12345678);
内容的提问来源于stack exchange,提问作者Dylan

