浮点与 SIMD(x87 / SSE / AVX)
x86 有三条浮点体系:传统的 x87 FPU(栈式)、SSE(XMM 寄存器,标量+打包)、AVX(YMM/ZMM,更宽)。现代代码优先用 SSE/AVX。
若裸
as报no such instruction(发行版默认未开启某些扩展),改用gcc -mavx2 -c foo.s等方式汇编,见模块二。
x87 FPU(栈式协处理器)
8 个 80 位寄存器构成环形栈:%st(0)(栈顶)…%st(7)。AT&T 中写为 %st 或 %st(0)。
| 指令 | 作用 |
|---|---|
fld |
压栈(内存/寄存器 -> st(0)) |
fst / fstp |
存储(p 版本存储后弹出栈顶) |
fild / fistp |
整数 load/store(自动转换) |
fadd / faddp |
加法 |
fsub / fsubp |
减法 |
fmul / fmulp |
乘法 |
fdiv / fdivp |
除法 |
fsqrt |
平方根 |
fsin / fcos |
三角函数 |
fcom / fcompp |
比较(结果写入状态字,需 fstsw 再 sahf) |
finit |
初始化 FPU |
x87 用后缀区分精度:s=单精度(32)、l=双精度(64)、t=扩展(80)。例如 flds(单)、fldl(双)。
finit
fldl a # st(0) = a
fldl b # st(0) = b,a 下移到 st(1)
faddp # st(1) = st(1) + st(0) 后弹栈 -> st(0) = a+b
fstpl result # 存回并弹栈
x87 的二元指令按 AT&T 语义看操作数位置:
fadd是st(0) += 源;无操作数的faddp是st(1) += st(0)再弹栈。
SSE(XMM0–XMM15,128 位)
SSE 提供打包单/双精度浮点及整数 SIMD 操作。标量操作带 ss/sd(scalar single/double),打包带 ps/pd(packed)。
| 指令族 | 示例 | 作用 |
|---|---|---|
| 数据传送 | movss, movsd, movaps, movups |
标量/打包传送(a=对齐,u=不对齐) |
| 标量算术 | addss, addsd, subsd, mulsd, divsd |
单/双精度标量运算 |
| 打包算术 | addps, addpd, mulps, mulpd |
4×float / 2×double 并行 |
| 比较 | comiss, ucomiss, cmppd |
标量/打包比较(设 EFLAGS) |
| 转换 | cvtsi2sd, cvtsd2si, cvtss2sd |
整数<->浮点,精度间转换 |
# 标量双精度:c = a + b
movsd a(%rip), %xmm0
addsd b(%rip), %xmm0
movsd %xmm0, c(%rip)
# 打包单精度:对 4 个 float 同时加
movaps va(%rip), %xmm0
addps vb(%rip), %xmm0
movaps %xmm0, vr(%rip)
注意事项:
movsd有歧义:既是 SSE 标量双精度传送,也是串指令(模块十一)的"复制双字"。GAS 按操作数类型区分,阅读反汇编时需看上下文。movss/movsd寄存器→寄存器传送不清空目的寄存器的高位(保留旧值);内存→寄存器传送则清零高位。清空整个 XMM 寄存器用惯用法pxor %xmm0, %xmm0。- SSE 整数指令(MMX 被 SSE 取代):
paddb/paddw/paddd/paddq、psubd、pmullw等打包整数运算。
AVX(YMM0–YMM15,256 位;AVX-512 为 ZMM 512 位)
AVX 引入三操作数、非破坏性指令(目的独立于两个源),使用 VEX 编码。
vaddps %ymm0, %ymm1, %ymm2 # ymm2 = ymm0 + ymm1
v前缀:AVX 指令。- AT&T 顺序:
vaddps src1, src2, dst(两个源在前,目的在后)。 - 256 位可一次处理 8×float 或 4×double。
- AVX-512 增加掩码寄存器
%k0–%k7、嵌入式广播、合并/零化谓词。
# AVX 求和 8 个 float
vmovaps va(%rip), %ymm0
vaddps vb(%rip), %ymm0, %ymm0
vmovaps %ymm0, vr(%rip)
浮点比较与条件跳转
comiss/ucomiss(单)、comisd/ucomisd(双)把结果写入 EFLAGS,之后可用普通 ja/jb/je 等跳转(u 版本在 NaN 时不触发异常):
ucomisd %xmm1, %xmm0 # 比较 xmm0 与 xmm1
ja above # xmm0 > xmm1(严格大于,已排除 NaN)
jb below # xmm0 < xmm1 或无序(NaN)
je equal # xmm0 == xmm1
判断大于必须用
ja(CF=0 且 ZF=0),它同时排除了 NaN;jb(CF=1)会把 NaN 也判进来。
数据对齐
SIMD 加载要求或强烈建议对齐:movaps(16 字节对齐)、vmovaps(32 字节对齐)在错对齐时 #GP 异常;movups/vmovups 允许不对齐但较慢。
.section .data
.align 16 # 或 .p2align 4
vec: .float 1.0, 2.0, 3.0, 4.0