浮点与 SIMD(x87 / SSE / AVX)

x86 有三条浮点体系:传统的 x87 FPU(栈式)、SSE(XMM 寄存器,标量+打包)、AVX(YMM/ZMM,更宽)。现代代码优先用 SSE/AVX。

若裸 asno 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 比较(结果写入状态字,需 fstswsahf
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 语义看操作数位置:faddst(0) += 源;无操作数的 faddpst(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/paddqpsubdpmullw 等打包整数运算。

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