x87 について

x87
x87 浮動小数点命令を使うと、80bit の拡張倍精度で、浮動小数点数の演算を行うことができます。

x64 に、x87 (FPU) は含まれています。
ただし、x64 における浮動小数点数の演算には、基本的に SSE 命令が使われます。

x87 では、独立した x87 レジスタを使用するため、汎用命令や SSE 命令などと並行して実行できるのが利点です。
ただし、MMX では、x87 レジスタの下位 64bit を、MMX レジスタとして使用するため、MMX 命令と同時に使用することはできません。

単精度 (float)・倍精度 (double) の浮動小数点数、整数、パック BCD 整数値の各形式の値は、拡張倍精度の浮動小数点数に変換してから、x87 レジスタに読み込むことができます。

なお、x87 命令は、基本的に、x87 レジスタ以外のレジスタと直接値をやりとりすることができません。
そのため、他のレジスタに演算結果の値を入れたい場合は、一度スタックなどのメモリ領域に値を格納してから、それを読み込む形になります。
x87 で使用されるレジスタ
データレジスタFPR0〜FPR7 (80bit)
※上記の名前を直接使用することはできません。
命令ポインタrIP (64bit)
データポインタrDP (64bit)
x87 オペコード11bit
コントロールワード16bit
精度と丸めの設定。
ステータスワード16bit
例外フラグ、TOP、演算結果のフラグが含まれます。
タグワード16bit
各データレジスタの値の状態を示す、2bit x 8 のデータ。
データレジスタ
浮動小数点の値を格納できる x87 のデータレジスタは、8個ありますが、他のレジスタとは異なり、8つのレジスタがスタック構造であるかのように使われます。

そのため、レジスタの固有名を直接指定するのではなく、スタック構造として見た時のインデックス値によって、ST(i) で参照する形になります。
※NASM では、オペランドで指定する場合、st0〜st7 の名前を使用します。
スタック構造
FPR0 ST(6)
FPR1 ST(7)
FPR2 ST(0) <- TOP = 2
FPR3 ST(1)
FPR4 ST(2)
FPR5 ST(3)
FPR6 ST(4)
FPR7 ST(5)

x87 の初期状態では、ST(0) は FRP0 を参照します。

x87 のステータスワード・レジスタの bit 13:11 (3bit) には、TOP の値が格納されており、0〜7 の値で、ST(0) に対応するレジスタのインデックス位置が指定されます。

例えば、TOP = 2 の場合、ST(0) では FPR2、ST(1) では FPR3 のレジスタを参照することになります。

TOP は、任意でインクリメント・デクリメントすることもでき、値のロード/ストアの命令によっても、自動的に変更されます。

このスタック構造は、8つのデータが循環している形になるので、FPR7 の次の位置は FPR0 になります。
プッシュとポップ
x87 命令における、レジスタの「プッシュ」と「ポップ」は、レジスタへの値の出し入れを意味します。

プッシュは、TOP をデクリメントして、レジスタスタックの新しい位置に値を入れます。
ポップは、レジスタスタックの先頭の値を解放して空にした後、TOP をインクリメントして、位置を一つ前に戻します (先頭の値の取り出し、または先頭の値の破棄)。

各レジスタでは、値が空かどうかが、タグワードレジスタによって指定されているので、空ではないレジスタに新しい値をプッシュすることはできませんし、空のレジスタから値を取り出すこともできません。

プッシュ
例えば、x87 の初期状態 (TOP = 0) から、FLDZ 命令を使って、+0.0 の値をプッシュした場合、まず、TOP がデクリメントされて 7 になった後、FPR7 [ST(0)] に、+0.0 の値が格納されます。
※レジスタは8つで循環しているので、(0 - 1) & 7 = 7 になります。

次に、FLD1 命令を使って、+1.0 の値をプッシュした場合、同様に TOP をデクリメントして、TOP = 6 になった後、FPR6 [ST(0)] に、+1.0 の値が格納されます。

FPR6 ST(7) : <空>
FPR7 ST(0) : +0.0  <- TOP = 7
 ↓
FPR6 ST(0) : +1.0  <- TOP = 6
FPR7 ST(1) : +0.0

このように、最後にレジスタにプッシュされた値は、常に ST(0) で参照でき、その一つ前の値は ST(1) で参照できます。

ポップ
FSTP 命令を実行した場合、ST(0) の値を、指定された場所にコピーした後、ST(0) に対応するレジスタが解放されて、値が空に設定され、TOP がインクリメントされます。

つまり、レジスタスタックの先頭に格納されている値を取り出して、一つ前の位置に戻ります。

FPR6 ST(0) : +1.0  <- TOP = 6
FPR7 ST(1) : +0.0
 ↓
FPR6 ST(7) : <空>
FPR7 ST(0) : +0.0  <- TOP = 7
ステータスワード・レジスタ (FSW)
ステータスワード・レジスタは、16bit です。
例外のフラグや TOP などがセットされるレジスタです。

|15|14|13-11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0|bit
| B|C3| TOP |C2|C1|C0|ES|SF|PE|UE|OE|ZE|DE|IE|

すべてのビットは読み書き可能ですが、B と ES に書き込まれた値は無視されます。
IE、DE、ZE、OE、UE、PE、SF は、明示的にクリアされるまで、ビットはそのままになります。

IE無効な操作の例外。
無効なオペランドやスタックフォールトなど、さまざまな種類のエラーによって発生します。
スタック障害によって IE 例外が発生すると、SF ビットも設定されます。
DE非正規化オペランド例外。
命令のソースオペランドの 1 つが、非正規化形式である場合、1。
ZEゼロ除算例外。
ゼロ以外の数値を、ゼロで除算する場合、1
OEオーバーフロー例外。
丸められた結果の絶対値が、宛先フォーマットで表現可能な最大値より大きい場合、1。
UEアンダーフロー例外。
丸められた非ゼロの結果の絶対値が小さすぎて、宛先フォーマットで表現できない場合、1。
UM ビットによってマスクされると、UE が精度例外 (PE) とともに発生した場合にのみ、UE 例外を報告します。
PE精度例外。
丸め後の浮動小数点の結果が、精度的に、宛先フォーマットで正確に表現できない場合、1。
SFスタックフォールト。
スタックオーバーフロー (空ではないスタックレジスタへのプッシュまたはロード) または、スタックアンダーフロー (空のスタックレジスタの参照) が発生した場合、1。
いずれかが発生すると、IE フラグも設定し、C1 ビットを書き込むことで、オーバーフローとアンダーフローを区別します (オーバーフローの場合は C1 = 1、アンダーフローの場合は C1 = 0)。
ES例外ステータス。
各命令時にこのビットの値を計算し、1 つ以上のマスクされていない浮動小数点例外が発生すると、ビットを 1 に設定します。
ES ビットがすでに設定されている場合、次の非制御 x87 または 64bit メディア命令が実行されるときに、#MF 例外ハンドラを呼び出します。
TOPレジスタのスタックの先頭位置を示す値。
C0
C1
C2
C3
算術や比較などの命令の結果に従って、これらのビットを設定します。
B浮動小数点ユニットがビジー。
ES ビットと同じ値に設定します。
8087 コプロセッサとの下位互換性を維持するためにのみ含まれており、コプロセッサがビジーであることを示します。
コントロールワード・レジスタ (FCW)
コントロールワード・レジスタ (16bit) は、ソフトウェア側が値を設定することで、丸め、精度、x87 浮動小数点例外のマスクを指定できます。

|15-13|12|11 10|9 8|7  6| 5| 4| 3| 2| 1| 0|bit
|予約 | Y|  RC | PC|予約|PM|UM|OM|ZM|DM|IM|

予約ビットを除く、すべてのビットは読み書き可能です。

bit 5:0例外マスク (PM, UM, OM, ZM, DM, IM)
対応する 6 種類の x87 浮動小数点例外をマスクできます。
ビットを 1 に設定するとマスクし、0 にクリアするとマスクを解除します。

例外をマスクすると、後続のすべてのインスタンスを、デフォルトの方法で処理します。
例外のマスクを解除すると、例外が発生したときに、プロセッサが #MF 例外サービスルーチンに分岐します。
PCx87 浮動小数点計算の精度を指定できます。
F(I)ADDx、F(I)SUBx、F(I)MULx、F(I)DIVx、FSQRT 命令にのみ影響します。

00b: 単精度
01b: 予約
10b: 倍精度
11b: 拡張倍精度 (default)
RCx87 命令の結果を、どのように丸めるかを指定できます。
比較と剰余を除く、すべての算術演算に適用されます。
非数値 (NaN) の結果を生成する操作には影響しません。

00b: 近い方向に丸め (default)
同等に近い場合は、偶数値 (最下位ビット) が採用されます。
01b: 切り捨て
10b: 切り上げ
11b: ゼロ方向に丸める
Yこのビットは廃止されました。
読み書きは可能ですが、値には意味がありません。
タグワード・レジスタ (FTW)
x87 タグワード・レジスタ (16bit) には、x87 レジスタごとに、2bit のタグが含まれています。

|15 14|13 12|11 10|9  8|7  6|5  4|3  2|1  0|bit
| FPR7| FPR6| FPR5|FPR4|FPR3|FPR2|FPR1|FPR0|

[値] 0 = 有効, 1 = ゼロ, 2 = 特殊 (NaN など), 3 = 空

レジスタに有効な値が入っているかどうかが判断されます。

ソフトウェア側で変更することはできませんが、x87 環境をメモリに保存する命令では、このレジスタの値も保存されます。

MMX 命令によって、MMX レジスタの値が変更された場合、すべてのレジスタが空ではない状態になります。
ポインタとオペコード
残りの、x87 命令ポインタ、データポインタ、命令オペコードについては、制御命令を除くすべての x87 命令を実行した時、ハードウェアに保存されます。
ハードウェアに保存する方法は、ハードウェアの実装によって異なります。
命令ポインタ
命令ポインタ (64bit) の内容は、動作モードによって異なります。

64bit モードの場合、最後の非制御 x87 命令の、64bit RIP オフセットが含まれます。
16bit コードセグメント (CS) セレクタは保存されません。
x87 オペコード
11bit の命令オペコードは、最後に実行された非制御 x87 浮動小数点命令の、2 バイトの命令オペコードの値を保持します。

bit 10:8 = 最初のオペコードバイトの下位 3bit。
bit 7:0 = 2番目のオペコードバイト。

最初のオペコードバイトの上位 5bit は、x87 命令では常に 1101_1xxxb であるため、それは除外されます。
データポインタ
データポインタ (64bit) の内容は、動作モードによって異なります。

64bit モードの場合、最後に実行された非制御 x87 命令によってアクセスされた、最後のメモリオペランドの 64bit オフセットが含まれます。
浮動小数点フォーマット
* 単精度 (32bit)
| 31   | 30      23 | 22          0 |
| 符号 | 指数(8bit) | 仮数部(23bit) |

* 倍精度 (64bit)
| 63   | 62       52 | 51          0 |
| 符号 | 指数(11bit) | 仮数部(52bit) |

* 拡張倍精度 (80bit)
| 79   | 78       64 | 63 | 62         0  |
| 符号 | 指数(15bit) |  I | 小数部(63bit) | I = 整数ビット
                     └── 仮数部 ───┘

* パックBCD整数 (80bit)
| 79   | 78     72 | 71           0 |
| 符号 | 無視 or 0 | 4x18桁 = 72bit |

すべての浮動小数点データ型は、符号 (0 = 正、1 = 負)、指数、数値の整数部と小数部を表す仮数部で構成されます。

パック BCD は、10進数の1桁 (0〜9) を、各 4bit で表す方法です。
サポートされている数値表現
x87 では、浮動小数点数の、以下の6つの表現がサポートされています。

  • 正規化
  • 非正規化
  • 擬似非正規化
  • ゼロ
  • 無限大
  • 非数 (NaN)

サポートされていない値をオペランドとして使用すると、無効操作例外 (IE) が発生します。

正規化数
正しく表現できる、有限の値です。
整数ビットが 1、指数が 0 か最大値でなく、小数部が表現可能な任意の値である、0 でない正または負の数です。

非正規化数
表現可能な最小の正規化数よりも小さい値です。
整数ビットが 0、指数が 0、小数部が 0 ではない、正または負の数です。

非正規化数が使用された場合、非正規化オペランド例外 (DE) を生成します。
丸められたゼロ以外の結果が、非正規化数として表現された場合、アンダーフロー例外 (UE) を生成する場合があります。
丸め後の結果が、最小の非正規化数として表すことができないほど小さすぎる場合、その結果はゼロとして表されます。

擬似非正規化数
整数ビットが 1、指数が 0、小数部が任意の値である、正または負の数です。

擬似非正規化数は値として受け入れられますが、擬似非正規化数の結果は生成しません。
ソースオペランドとして使用される場合、非正規化オペランド例外 (DE) を生成します。

ゼロ
整数ビットが 0、指数が 0、小数部が 0 である、正または負の数です。
ゼロの符号がどうなるかは、実行される演算と、丸めモードによって異なります。

無限大
整数ビットは 1、指数は最大値、小数部は 0 である、無限に大きい正か、無限に小さい負の値です。

「0 ではなく無限大でない数を、0 で割る」「無限大で乗算する」「無限大を、無限大または 0 で加算する」場合、無限大の結果が生成されます。
無限大を演算した結果は、正しくなります。

非数値 (NaN)
NaN は非数値であり、表現可能な値の範囲外にあります。
整数ビットは 1、指数は最大、小数部は非ゼロです。

NaN には、SNaN と QNaN の2つのタイプがあります。
QNaN は、小数部の最上位ビットが 1 の NaN。
SNaN は、小数部の最上位ビットが 0 の NaN です。