x86 汇编/浮点数
ALU 只能处理整数值。虽然整数对于某些应用程序来说已经足够了,但通常需要使用小数。一个高度专业化的协处理器,是 FPU(浮点单元)的一部分,将允许您操作带有小数部分的数字。
最初的 x86 家族成员拥有一个独立的数学协处理器,用于处理浮点运算。原始的协处理器是 8087,从那时起,所有 FPU 都被称为“x87”芯片。后来,变体将 FPU 集成到微处理器本身。能够管理浮点数意味着几件事
- 微处理器必须有空间来存储浮点数。
- 微处理器必须有指令来操作浮点数。
即使 FPU 被集成到 x86 芯片中,它仍然被称为“x87”部分。例如,该主题的文献通常将 FPU 寄存器堆栈称为“x87 堆栈”,并且 FPU 操作通常被称为“x87 指令集”。
可以使用 cpuid
指令检查集成 x87 FPU 的存在。
; after you have verified
; that the cpuid instruction is indeed available:
mov eax, 1 ; argument request feature report
cpuid
xor rax, rax ; wipe clean accumulator register
bt edx, rax ; CF ≔ edx[rax] retrieve bit 0
setc al ; al ≔ CF
FPU 有一个包含 *8 个* 寄存器的数组,可以作为堆栈访问。有一个 *top* 索引指示堆栈的当前顶部。将项目推入或弹出堆栈只会更改 *top* 索引,并分别存储或擦除数据。
st(0)
或简称为 st
指的是当前位于堆栈顶部的寄存器。如果堆栈上存储了 8 个值,则 st(7)
指的是堆栈上的最后一个元素(即底部)。
数字从 *内存中* 推入堆栈,并从堆栈弹出回 *内存中*。没有指令允许直接将值传输到或从 ALU 寄存器。x87 堆栈只能通过 FPU 指令访问 - 您不能编写 mov eax, st(0)
- 有必要将值存储到内存中,例如,如果您想打印它们。
FPU 指令通常会从堆栈中弹出前两个项目,对它们进行操作,并将答案推回堆栈顶部。
浮点数通常可以是 32 位长,即编程语言 C 中的 float
数据类型,或者 64 位长,即 C 中的 double
。但是,为了减少舍入误差,FPU 堆栈寄存器都 *80 位宽*。
大多数 调用约定 在 st(0)
寄存器中返回浮点值。
以下程序(使用 NASM 语法)计算 123.45 的平方根。
[org 0x7c00]
[bits 16]
global _start
section .data
val: dq 123.45 ; define quadword (double precision)
section .bss
res: resq 1 ; reserve 1 quadword for result
section .text
_start:
;initilizes the FPU, avoids inconsistent behavior
fninit
; load value into st(0)
fld qword [val] ; treat val as an address to a qword
; compute square root of st(0) and store the result in st(0)
fsqrt
; store st(0) at res, and pop it off the x87 stack
fstp qword [res]
; the FPU stack is now empty again
; end of program
本质上,使用 FPU 的程序使用 fld
及其变体将值加载到堆栈上,对这些值执行操作,然后使用 fst
的一种形式将它们存储到内存中,最常见的是在您完成 x87 后使用 fstp
,以根据大多数调用约定清理 x87 堆栈。
这是一个更复杂的示例,它评估 余弦定律
;; c^2 = a^2 + b^2 - cos(C)*2*a*b
;; C is stored in ang
global _start
section .data
a: dq 4.56 ;length of side a
b: dq 7.89 ;length of side b
ang: dq 1.5 ;opposite angle to side c (around 85.94 degrees)
section .bss
c: resq 1 ;the result ‒ length of side c
section .text
_start:
fld qword [a] ;load a into st0
fmul st0, st0 ;st0 = a * a = a^2
fld qword [b] ;load b into st0 (pushing the a^2 result up to st1)
fmul st0, st0 ;st0 = b * b = b^2, st1 = a^2
faddp ;add and pop, leaving st0 = old_st0 + old_st1 = a^2 + b^2. (st1 is freed / empty now)
fld qword [ang] ;load angle into st0. (st1 = a^2 + b^2 which we'll leave alone until later)
fcos ;st0 = cos(ang)
fmul qword [a] ;st0 = cos(ang) * a
fmul qword [b] ;st0 = cos(ang) * a * b
fadd st0, st0 ;st0 = cos(ang) * a * b + cos(ang) * a * b = 2(cos(ang) * a * b)
fsubp st1, st0 ;st1 = st1 - st0 = (a^2 + b^2) - (2 * a * b * cos(ang))
;and pop st0
fsqrt ;take square root of st0 = c
fstp qword [c] ;store st0 in c and pop, leaving the x87 stack empty again ‒ and we're done!
; don't forget to make an exit system call for your OS,
; or execution will fall off the end and decode whatever garbage bytes are next.
mov eax, 1 ; __NR_exit
xor ebx, ebx
int 0x80 ; i386 Linux sys_exit(0)
;end program
您可能会注意到,以下某些指令在名称上只差一个字母:在末尾附加一个 **P**。该后缀表示除了执行正常操作之外,它们还会在执行完成后 **P**op x87 堆栈。
FDISI, FENI, FLDENVW, FLDPI, FNCLEX, FNDISI, FNENI, FNINIT, FNSAVEW, FNSTENVW, FRSTORW, FSAVEW, FSTENVW
fld
: 加载浮点数fild
: 加载整数fbld
fbstp
- 在堆栈顶部加载一个常数
fld1
:fldld2e
:fldl2t
:fldlg2
:flln2
:fldz
: “正”
fst
,fstp
fist
,fistp
: 存储整数fxch
: 交换fisttp
: 存储截断的整数
算术指令
[edit | edit source]fabs
: 绝对值fchs
: 改变符号fxtract
: 拆分指数和有效数
fadd
,faddp
,fiadd
: 加法fsub
,fsubp
,fisub
: 减法fsubr
,fsubrp
,fisubr
: 反向减法
fmul
,fmulp
,fimul
fsqrt
: 平方根fdiv
,fdivp
,fidiv
: 除法(另见fdiv
维基百科上的错误)fdivr
,fdivrp
,fidivr
fprem
: 部分余数fptan
fpatan
frndint
: 四舍五入为整数fscale
: 乘以/除以 2 的整数次幂f2xm1
:fyl2x
:fyl2xp1
:
FPU 内部和其他指令
[edit | edit source]finit
: 初始化 FPUfldcw
flenv
frstor
fsave
,fnsave
fstcw
,fnstcw
fstenv
,fnstenv
fstsw
,fnstsw
finccstp
和fdecstp
: 增加或减少 topffree
: 将寄存器标记为可用
ftst
: 测试fcom
,fcomp
,fcompp
: 比较浮点数ficom
,ficomp
: 与整数比较fxam
: 检查寄存器
fclex
: 清除异常fnop
fwait
与wait
相同。
在特定处理器中添加
[edit | edit source]随 80287 添加
[edit | edit source]FSETPM
随 80387 添加
[edit | edit source]FCOS, FLDENVD, FNSAVED, FNSTENVD, FPREM1, FRSTORD, FSAVED, FSIN, FSINCOS, FSTENVD, FUCOM, FUCOMP, FUCOMPP
随奔腾 Pro 添加
[edit | edit source]FCMOVB, FCMOVBE, FCMOVE, FCMOVNB, FCMOVNBE, FCMOVNE, FCMOVNU, FCMOVU, FCOMI, FCOMIP, FUCOMI, FUCOMIP, FXRSTOR, FXSAVE
FXRSTOR,FXSAVE
这些指令也在没有 SSE 支持的后期奔腾 II 中得到支持
FISTTP(x87 到整数转换,无论状态字如何都进行截断)
ffreep
: 执行ffree st(i)
并弹出堆栈