ぐだぐだ低レベルプログラミング(215)x86(16bit)、浮動小数点数の加算

Joseph Halfmoon

前回は8087に浮動小数値をロード。その度にレジスタ・スタックは積みあがっていきます。今回は、浮動小数の加算。演算結果はスタックトップに書きこむのですが、スタックトップ位置はそのままの場合と、ポップして1個少なくなる場合あり。それに忘れちゃいけないx86はCISCデス。FADDでメモリを参照することも可。

※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら

※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。

    •  Windows 11 PC (i5-1235U)
    •  Ubuntu 24.04 LTS on WSL2
    •  QEMU 8.2.2
    •  FreeDOS 1.3

※オリジナルの8086+8087の組み合わせでは8086と8087の同期をとるためにWAIT命令が必用になる場合があります。後継機種ではWAIT命令が不要(演習に使用しているエミュレータQEMUでも不要)なのでWAIT命令は使用しません。メンドイし。

FADD

過去回で復習しましたが8087のレジスタは80ビット幅あり、単精度浮動小数(32ビット幅)、倍精度浮動小数(64ビット幅)のどちらでも、レジスタにロードしてしまえば内部の80ビット幅表現となります。大は小を兼ねると、ホントか。

しかしFADD命令に2種類あり、一方のFADD命令は、スタックトップとトップ下(あるいは2つ下など)を足してスタックトップに結果を返すもの。他方のFADDP命令は、スタックトップとトップ下(あるいは2つ下など)を足すところまでは一緒ですが、返す先はトップ下(あるいは2つ下など)となり、それに加えてスタックトップを「ポップ」してスタックを縮めてくれる命令です。この末尾の

の文字は、8087命令に共通して現れる文字であります。これが出てきたらスタックはポップするのだと。

さて、8087も86の一族であります。バリバリのCISCです。これはFADD命令でも一貫しとります。演算ソースとしてメモリをとることができます。ただし、FADD命令の場合だけで、FADDPの場合は無。

今回実験のプログラム

強力なx86用アセンブラNASM用のソースです。3回重ねて浮動小数点加算をしているコードです。加算内容は以下のとおり

    1. メモリにおいてあった2.125と定数ロードした1.0を足し算
    2. 上記の足し算の結果にメモリにおいてあった1.125を足し算
    3. さらに上記の結果に定数ロードした1.0を足し算

なお、NASMアセンブラと古式ゆかしいインテル式では8087のレジスタスタックの記述法に以下の差異があるみたい。

    • インテル式のレジスタスタック記法、fadd st, st(1)
    • NASM式のレジスタスタック記法、fadd st0, st1

コマケー話なんだが。

segment code

..start:
    mov ax, data
    mov ds, ax
    mov ax, stack
    mov ss, ax
    mov sp, stacktop
test:
    mov bx, src1
    fld dword [bx]
    fld1
    fadd st0, st1
    fadd dword [src2]
    fld1
    faddp st1, st0
fin:
    mov ax, 0x4c00
    int 0x21
    resb    2048

segment data    align=16
    resb    1024 * 63
src1: dd 2.125
src2: dd 1.125

segment stack   class=STACK align=16
    resb    2048
stacktop:
    dw 1024 dup (0)
stackend:
アセンブルして実行

MS-DOS互換で機能強化されているフリーなFreeDOS上、以下のステップで上記のアセンブラソース fadd.asm から実行可能なオブジェクトファイルを生成して実行することができます(nasmとwatcom Cがインストール済であること。)

なお、FreeDOS付属の「debugx」デバッガは、8087のレジスタ内容を浮動小数で見せてくれます。

nasm -f obj -l fadd.lst fadd.asm
wlink name fadd.exe format dos file fadd.obj
debugx fadd.exe

まずは、オブジェクトコードの逆アセンブルからFADD_uEC

赤枠が最初のFADD命令です。NASM用のソースではNASM式に記していたですが、debugxの逆アセンブルではインテル式に直ってます。

次の緑枠が2番目のFADD命令です。NASM上では、ダイレクトアドレシングのポインタは dword と表記でしたが、これまたインテル式の FLOAT PTR に直ってます。なお、古のインテル式ではPTRの先のダイレクトアドレスについてラベルで書く場合、鍵括弧がいらなかったような。でもマイクロソフト式は要るのだったっけか。この辺記憶が怪しいデス。テキトーに書いてアセンブル通ったら正解ということで。いいのか、それで。

三番目の黄色枠がFADDP命令です。NASMのソース的には、ちゃんとレジスタスタックの形式で記したのですが、ディスアセンブルではただのFADDPになってます。まあ、これがデフォってことね。

まずは最初の単精度小数(float型)のロード。赤枠が命令。下の方のST0に値がロードされとります。FLD

続いての定数1.0 のロード命令が以下の緑枠に。FLD1

上記のように、スタックトップに1.0、トップ下(ST1)に2.125が入ってます。

最初のFADDが以下に。これにより上の1.0+2.125の結果3.125がスタックトップに入ります。FADD_1

上記のように、トップ下の値は変りませぬ。

さて以下ではメモリから直接オペランド(1.125)をとってきて、現在のスタックトップ3.125と足し合わせます。FADD_2

上記のように、スタック・トップは4.125、そしてスタックの深さは変りませぬ。

この後、FLD1命令を発行し、スタックトップに1を追加した後の加算はFADDPです。スタックトップに1.0、トップ下が4.25となってます。FADD_3

加算結果5.25がスタックトップにありますが、スタックの深さは一つ減ってます。上の緑枠は「回収」、黄色枠のところがスタックトップになった感じっす。

加算は出来た、後は以下同文か?

ぐだぐだ低レベルプログラミング(214)x86(16bit)、浮動小数点数のロード へ戻る

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です