
前回は8087に浮動小数値をロード。その度にレジスタ・スタックは積みあがっていきます。今回は、浮動小数の加算。演算結果はスタックトップに書きこむのですが、スタックトップ位置はそのままの場合と、ポップして1個少なくなる場合あり。それに忘れちゃいけないx86はCISCデス。FADDでメモリを参照することも可。
※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら
※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。
※オリジナルの8086+8087の組み合わせでは8086と8087の同期をとるためにWAIT命令が必用になる場合があります。後継機種ではWAIT命令が不要(演習に使用しているエミュレータQEMUでも不要)なのでWAIT命令は使用しません。メンドイし。
FADD
過去回で復習しましたが8087のレジスタは80ビット幅あり、単精度浮動小数(32ビット幅)、倍精度浮動小数(64ビット幅)のどちらでも、レジスタにロードしてしまえば内部の80ビット幅表現となります。大は小を兼ねると、ホントか。
しかしFADD命令に2種類あり、一方のFADD命令は、スタックトップとトップ下(あるいは2つ下など)を足してスタックトップに結果を返すもの。他方のFADDP命令は、スタックトップとトップ下(あるいは2つ下など)を足すところまでは一緒ですが、返す先はトップ下(あるいは2つ下など)となり、それに加えてスタックトップを「ポップ」してスタックを縮めてくれる命令です。この末尾の
P
の文字は、8087命令に共通して現れる文字であります。これが出てきたらスタックはポップするのだと。
さて、8087も86の一族であります。バリバリのCISCです。これはFADD命令でも一貫しとります。演算ソースとしてメモリをとることができます。ただし、FADD命令の場合だけで、FADDPの場合は無。
今回実験のプログラム
強力なx86用アセンブラNASM用のソースです。3回重ねて浮動小数点加算をしているコードです。加算内容は以下のとおり
-
- メモリにおいてあった2.125と定数ロードした1.0を足し算
- 上記の足し算の結果にメモリにおいてあった1.125を足し算
- さらに上記の結果に定数ロードした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命令です。NASM用のソースではNASM式に記していたですが、debugxの逆アセンブルではインテル式に直ってます。
次の緑枠が2番目のFADD命令です。NASM上では、ダイレクトアドレシングのポインタは dword と表記でしたが、これまたインテル式の FLOAT PTR に直ってます。なお、古のインテル式ではPTRの先のダイレクトアドレスについてラベルで書く場合、鍵括弧がいらなかったような。でもマイクロソフト式は要るのだったっけか。この辺記憶が怪しいデス。テキトーに書いてアセンブル通ったら正解ということで。いいのか、それで。
三番目の黄色枠がFADDP命令です。NASMのソース的には、ちゃんとレジスタスタックの形式で記したのですが、ディスアセンブルではただのFADDPになってます。まあ、これがデフォってことね。
まずは最初の単精度小数(float型)のロード。赤枠が命令。下の方のST0に値がロードされとります。
上記のように、スタックトップに1.0、トップ下(ST1)に2.125が入ってます。
最初のFADDが以下に。これにより上の1.0+2.125の結果3.125がスタックトップに入ります。
上記のように、トップ下の値は変りませぬ。
さて以下ではメモリから直接オペランド(1.125)をとってきて、現在のスタックトップ3.125と足し合わせます。
上記のように、スタック・トップは4.125、そしてスタックの深さは変りませぬ。
この後、FLD1命令を発行し、スタックトップに1を追加した後の加算はFADDPです。スタックトップに1.0、トップ下が4.25となってます。
加算結果5.25がスタックトップにありますが、スタックの深さは一つ減ってます。上の緑枠は「回収」、黄色枠のところがスタックトップになった感じっす。
加算は出来た、後は以下同文か?