ぐだぐだ低レベルプログラミング(233)x86(16bit)、FPU制御命令

Joseph Halfmoon

前回は8087ストア命令を練習。いつものように命令セットに非対称性がありました。しかし今回のあれやこれやに比べたらまだましだったかもしれません。今回はずっと顔を背けてきた(その割に使ってしまった過去回もあるけど)FPUの制御命令を一挙に列挙してみたいと思います。今回は強烈なメンバどもを拝観するだけ、練習はまた今度。

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

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

    •  Windows 11 PC (i5-1235U)
    •  Ubuntu 24.04 LTS on WSL2
    •  QEMU 8.2.2
    •  FreeDOS 1.3
FPU制御命令

以下に一覧を掲げます。

mnemonic operand 命令動作
FINIT/FNINIT FPUの初期化
FDISI/FNDISI 割り込み禁止
FENI/FNENI 割り込み許可
FLDCW mem16 CWへのロード
FSTCW/FNSTCW mem16 CWのストア
FSTSW/FNSTSW mem16 SWの(メモリへの)ストア
FSTSW/FNSTSW AX SWをAXに転送
FCLEX/FNCLEX 例外条件クリア
FSTENV/FNSTENV mem environmentのストア
FLDENV mem environmentのロード
FSAVE/FNSAVE mem stateのストア
FRSTOR mem stateのレストア
FINCSTP スタックポインタのインクリメント
FDECSTP スタックポインタのデクリメント
FFREE st レジスタ開放
FNOP ノーオペレーション
FWAIT CPUをwait信号待ちさせる

上記を読み解くのに、8087系FPUのレジスタを覚えておくこと必須です。以下の過去回にてレジスタの説明してます。

ぐだぐだ低レベルプログラミング(213)x86(16bit)、8087のレジスタ

二文字目に「N」付とそうでないもの2種類があるんだが

例えば冒頭のFINIT、これはFPUを初期化してハードウエアリセット後のデフォルト状態に戻す命令です。これに「FINIT」と「FNINIT」の2つが存在します。NなしのFINIT命令を使うと、まず数値例外条件(マスクされていないもの)が成立していないかどうかを先にチェックします。そこで例外成立していたら、無視するわけにもいかないのでその処理に飛ぶことになります。例外なければ本題の「初期化」をします。また、FPU制御のバスサイクルが進行中だった場合、その完了を待つみたいです。立つ鳥後を濁さず、後始末は綺麗にしてから、という感じでしょうかね。一方N付のNは、no-wait のNみたいです。進行中のバスサイクルあったらアボートされちゃうみたいだし、非マスクの数値例外条件のチェックなども行われません。バッサリ一刀両断。

environment と state って何だ?

ここまで8087系のFPUを動かしてきて、8087のレジスタ共はスタック構造にオーガナイズされているだけに、レジスタ同士の関係がディープで「全部のレジスタ」をまとめてロード、ストアしないとコンテキストスイッチなどできないじゃん、と思われたことと思います。実際そのための命令が用意されとります。

    • FSAVE / FNSAVE
    • FRSTOR

当然セーブがメモリへの書き出し、レストアがメモリからの読み出しです。このとき書き出されるのは以下のFPUレジスタ全てです(メモリの低い番地に置かれるものからの順番にて。)

    1. CW(制御ワード)
    2. SW(ステータスワード)
    3. TAG
    4. エラーポインタ(命令とオペランド)
    5. スタックトップ
    6. …スタックの途中の6本
    7. スタックボトム、ST(7)

上記の全てを称して state と唱えているみたいです。当然、メモリとのやりとりですが、ここでの mem はいつもとちょっと扱いが異なります。

x86の場合、フツーのメモリでは荘厳に

word ptr [bx]

などと「ポインタ」表現を行う決まりになってますが、ここでは「ポインタ」という表現をしないお約束らしいです。それにセーブされるバイト数が機種によって異なります。

8087 … 94 bytes
80387 … 108 bytes

 

8087も80387も、レジスタスタックのレジスタは80ビット幅が8本、SW、CW、TAGは16ビットです。しかしその差は何?

これについては、

    • 8086の主記憶空間を「乗っ取って」勝手にアクセスしている8087コープロセッサ
    • 単なる数値演算デバイスとして、80386にオペランドを餌付けされている80387

という関係を思い出さないとなりませぬ。FPU_Exception_Pointers

8087系FPUは、FPU例外を検出すると、例外を引き起こした命令のオペコードの「主要部」と、命令のアドレス、そして例外を引き起こすことになったオペランド(データ)のアドレスをエラーポインタに保存することになってました。しかし、保存の仕方は、8087と80387では決定的に違うのでした。

8087は8086の「物理メモリ」空間、20ビット幅のアドレスを自力で制御してます。よって、8087のアドレスは「物理アドレス」で20ビットです。一方、386はMMU(セグメンテーション+ページング)をもったマシンです。387にしたら「物理メモリ」空間などなんのこっちゃです。80386から教えてもらえるのは、「論理メモリ」空間(セグメンテーション通ってからページングを通るのでセレクタ+オフセット式の論理アドレスです)です。このため、アドレスのビット幅も違えば、保存すべき情報も異なるので8087ではメモリ上8バイト分を占めていたエラーポインタは、80387では16バイトと倍増してます。

まだ差が埋まっていないのをお気づき?後は比較的簡単デス。16ビットバス前提の8086の御供である8087では、CW、SW、TAGの16ビット幅レジスタを素のまま16ビット幅でストアしているのですが、32ビットバスを念頭においている80387では16ビット幅のレジスタの上位に0x0000を詰めて32ビット幅にしてからメモリに吐き出してます。これで差がうまった。

なお、このstate以外に、environment とよばれる構造を扱う命令がありますが、こちらは、CW、SW、TAG、エラーポインタ部分のみをそう呼ぶみたいです。レジスタスタック本体は含みませぬ。

1回で全部説明できなかった。制御レジスタはメンドイよ~。

ぐだぐだ低レベルプログラミング(232)x86(16bit)、FST、ストアあれこれ へ戻る

コメントを残す

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