跳轉到內容

x86 彙編/浮點數

來自華夏公益教科書,開放的書籍,開放的世界

ALU 只能處理整數值。雖然整數對於某些應用程式來說已經足夠了,但通常需要使用小數。一個高度專業化的協處理器,是 FPU(浮點單元)的一部分,將允許您操作帶有小數部分的數字。

x87 協處理器

[編輯 | 編輯原始碼]

最初的 x86 家族成員擁有一個獨立的數學協處理器,用於處理浮點運算。原始的協處理器是 8087,從那時起,所有 FPU 都被稱為“x87”晶片。後來,變體將 FPU 整合到微處理器本身。能夠管理浮點數意味著幾件事

  1. 微處理器必須有空間來儲存浮點數。
  2. 微處理器必須有指令來操作浮點數。

即使 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 暫存器堆疊

[編輯 | 編輯原始碼]

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 堆疊。

原始 8087 指令

[編輯 | 編輯原始碼]

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: 反向減法
  • finit: 初始化 FPU
  • fldcw
  • flenv
  • frstor
  • fsave, fnsave
  • fstcw, fnstcw
  • fstenv, fnstenv
  • fstsw, fnstsw
  • finccstpfdecstp: 增加或減少 top
  • ffree: 將暫存器標記為可用
  • ftst: 測試
  • fcom, fcomp, fcompp: 比較浮點數
  • ficom, ficomp: 與整數比較
  • fxam: 檢查暫存器
  • fclex: 清除異常
  • fnop
  • fwaitwait 相同。

在特定處理器中新增

[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

隨 SSE 新增

[編輯 | 編輯原始碼]

FXRSTOR,FXSAVE

這些指令也在沒有 SSE 支援的後期奔騰 II 中得到支援

隨 SSE3 新增

[編輯 | 編輯原始碼]

FISTTP(x87 到整數轉換,無論狀態字如何都進行截斷)

未公開指令

[編輯 | 編輯原始碼]
  • ffreep: 執行 ffree st(i) 並彈出堆疊

進一步閱讀

[編輯 | 編輯原始碼]
華夏公益教科書