ぐだぐだ低レベルプログラミング(217)x86(16bit)、浮動小数点数の比較と判断

Joseph Halfmoon

前回で8087の加減乗除は極めてしまったかと思いましたが早とちりデス。計算したら、計算結果を判断するという過程が必要。当然、8087には8087の比較命令というものが存在するのですが、8086側が司っているプログラム・フローに反映するのはどしたら良いの?ここで古代の遺物と蔑まれていた命令が活躍。ホントか?

※「ぐだぐだ低レベル プログラミング」投稿順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命令は使用しません。メンドイし。

浮動小数の比較命令

8087の浮動小数比較命令は以下の3つあります。いずれもスタックトップと他を比較して、その結果を「8087の」ステータスワードに反映するもの。

    • FCOM
    • FCOMP
    • FCOMPP

3種類もあるのは、無印=レジスタスタックに触れない、P1個=レジスタスタックから1個ポップ、P2個=レジスタスタックから2個ポップ、と3通りのレジスタ・スタック操作が出来るからであります。

結果は過去回にて図を掲げたステータスワードの「黄色の」4ビットC3からC0に反映されます。図の一部を切り出したものが以下に。statusWord

しかしこれは8087内部のフラグどもです。これに反映したとて、何もしなければ8086には全然伝わりませぬ。そして条件判断をするには8086から「見える」ところになければなりません。どうするの?

8087ステータスレジスタの8086フラグへの反映

さて、8087側の状態を8086に報告するために、以下の命令が用意されております。

    • FSTSW AX
    • FNSTSW AX

8087のレジスタのストアは基本、メモリ相手なのですが、上記の命令に関しては珍しくレジスタ間でのやりとりになってます。スペシャルな奴だね。そしてデスティネーションは8086らしいAXレジスタのキメウチ。

さて上記の2命令とも8087内の「ステータスワード」16ビットを8086のAXにコピーしてくれるのですが、ニーモニックが2つあるココロは以下です。

    • FSTSW、マスクされていない数値例外をチェック
    • FNSTSW、数値例外をチェックしない

8087の数値例外(これがまたいろいろあるのよ)はまだ練習していないので今回はパス。触らぬ神に祟りなし、今回はFNSTSW命令の方を使用させていただいております。

さて、8087のステータスワードがAXレジスタに転送できたからと言って落ち着いてもいられません。条件分岐するためにはAXの内容をFlags内のフラグどもに反映させねばならんのです。そこで活躍することになるのが以下の命令デス。

SAHF

AXレジスタの上半分(AHレジスタ)をFlagsの下位へ転送してくれる命令です。SAHFについて、お惚け老人は、過去回で御先祖の8ビット機との互換性をとるための遺物的な命令みたいにディスってました。すみません。私が悪うございました。ちゃんと対8087で活躍の局面がありましたな。

SAHFによる8087ステータスの転送を観察すると以下の3ビットのみ意味があることに気づきます。ビットはFlags内の位置です。

    • bit6=ZFに C3ビットが反映される
    • bit2=PFに C2ビットが反映される
    • bit0=CFに C0ビットが反映される

一応、C1ビットも転送はされるのですが、あまり役に立つ局面は無さそうに見えます、宛先がAFだし、ホントか?

また、PF(パリティ・フラグ)に転送されるC2が立っている条件は8種ある全てがNaN、無限大、デノーマル数など「フツーじゃない」奴らです。PFが立っているときは普通の比較ができんかったということであります。

比較結果の判断利用としては以下のとおり。

    • JA(ジャンプ・アバブ)でZF==0とCF==0確認できたら「大なり」
    • JB(ジャンプ・ビロー)でCF==1を確認できたら「小なり」
    • JE(ジャンプ・イコール)でZ==1を確認できたら「イコール」
今回実験のプログラム

1引くπ(3.14…)を8087で計算して、その結果で分岐(当然「小なり」となる予定)するだけのプログラムです。強力なx86用アセンブラNASM用のソースです。インテル式とはオペランドの表記法など微妙に異なってます。そのソースが以下に。

segment code

..start:
    mov ax, data
    mov ds, ax
    mov ax, stack
    mov ss, ax
    mov sp, stacktop
test:
    fldpi
    fld1
    fcom st1
    fnstsw ax
    sahf
    jp ERR_NEVER  ;NEVER
    je ST_EQ      ;NEVER
    ja ST_GT      ;NEVER
    jb ST_LT
    jmp fin       ;NEVER
ST_GT:
    nop
ERR_NEVER:
    nop
ST_EQ:
    nop
ST_LT:
    nop
fin:
    mov ax, 0x4c00
    int 0x21
    re:wqsb    2048

segment data    align=16
    resb    1024 * 63

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

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

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

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

まずはプログラムの逆アセンブルリストから。unasmFCOM_EC

上記の赤枠部分で判定して反映したフラグにたいして、緑枠のJB(ジャンプ・ビロウ)が反応すればめでたしっす。

まずは肝心のFCOM命令の実行のビフォーアフター。緑枠がビフォーで、赤枠がアフターです。FCOM自体はスタック操作は供わないのでレジスタスタックはそのまま、唯一異なるのはSW(ステータス・ワード)のビット8のところの「1」です。FCOMbeforeAfter

この「1」を反映させるってことだね。

続いて、FNSTSW AXで8087のSWを、8086のAXレジスタに転送するところ(赤枠。)そしてAXレジスタの上位8ビットをFlagsに反映させるSAHF命令の動作(緑枠。)FNSTSW_SAHF

実行前に、ZR、NA、PE、NCだったフラグどもが、実行後にNZ、AC、PO、CYとなっております。これだね。

さて上記のフラグ設定により。必然的にJBまで落ちてきて、そこでジャンプと。JB

良かった、予定どおりのところでジャンプしたぞなもし。

ぐだぐだ低レベルプログラミング(216)x86(16bit)、浮動小数点数の加減乗除 へ戻る

コメントを残す

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