要使 GCC 生成 AVX2 代码,可以使用命令行选项“-mavx2”。基于不同版本的 SSE 以及第一个版本的 AVX 的代码从概念上来说是类似的,不过指令名和格式有所不同。此外,同之前介绍的整数操作指令一样,这里也是使用 ATT 格式,它同 Intel 格式中列出的指令操作数的顺序是不同的。
如下图所示,AVX 浮点体系结构允许数据存储在 16 个 YMM 寄存器中,它们的名字为 %ymm0 ~ %ymm15。每个 YMM 寄存器都是 256 位(32 字节)。当对标量数据操作时,这些寄存器只保存浮点数,而且只使用低 32 位(对于 float)或 64 位(对于 double)。汇编代码用寄存器的 SSE XMM 寄存器名字 %xmm0 ~ %xmm15 来引用它们,每个 XMM 寄存器都是对应的 YMM 寄存器的低 128 位(16 字节)。
类似于整数操作指令,浮点指令也提供了一组在内存和 XMM 寄存器之间以及从一个 XMM 寄存器到另一个不做任何转换的传送浮点数的指令,如下图所示。
这里,引用内存的指令是标量指令,意味着它们只对单个而不是一组封装好的数据值进行操作。数据要么保存在内存中(由图中的 M32 和 M64 指明,分别表示 32 位和 64 位内存范围),要么保存在 XMM 寄存器中(在图中用 X 表示)。无论数据对齐与否,这些指令都能正确执行,不过代码优化规则建议 32 位内存数据满足 4 字节对齐,64 位数据满足 8 字节对齐。内存引用的指定方式与整数 MOV 指令的一样,包括偏移量、基址寄存器、变址寄存器和伸缩因子的所有可能的组合。
GCC 只用标量传送操作从内存传送数据到 XMM 寄存器或从 XMM 寄存器传送数据到内存。对于在两个 XMM 寄存器之间传送数据,GCC 会使用 vmovaps 传送单精度数,用 vmovapd 传送双精度数。对于这些情况,程序复制整个寄存器还是只复制低位值既不会影响程序功能,也不会影响执行速度,所以使用这些指令还是针对标量数据的指令没有实质上的差别。指令名字中的“a”表示“aligned(对齐的)”。当用于读写内存时,如果地址不满足 16 字节对齐,它们会导致异常。在两个寄存器之间传送数据,绝不会出现错误对齐的状况。
下面是一个浮点传送操作的 C 例子。
float float_mov(float v1, float *src, float *dst){ float v2 = *src; *dst = v1; return v2; }
与它相关联的 x86-64 汇编代码如下。
;float float_mov(float v1, float *src, float *dst) ;v1 in %xmm0, src in %rdi, dst in %rsi float_mov: vmovaps %xmm0, %xmm1 ; Copy v1 vmovss (%rdi), %xmm0 ; Read v2 from src vmovss %xmm1, (%rsi) ; Write v1 to dst ret ; Return v2 in %xmm0
可以看到它使用了 vmovaps 指令把数据从一个寄存器复制到另一个,使用了 vmovss 指令把数据从内存复制到 XMM 寄存器以及从 XMM 寄存器复制到内存。
参考书籍:《深入理解计算机系统》第三章——程序的机器级表示。