
2026/09/13 0:49
インテルの 8087 フローティングポイントチップにおけるマイクロコード:スケール命令について
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
本研究の最も重要な発見は、1980 年製のインテル製 8087 フローティングポイントコプロセッサの成功した深いリバースエンジニアリングであり、複雑な内部マイクロコードアーキテクチャを明らかにしたことにある。この成果は、チップが発売から数十年後の今でも高精度な数値演算をどのように制御していたのかというこれまでの謎を覆すものである。分析の結果、プロセッサはデータ転送やサブルーチン呼出といったハードウェア動作を指揮する 16 ビットのマイクロ指令を含む、1,648 ワードのリードオンリメモリ(ROM)を使用することが確認された。例えば、特殊ケース(ゼロや非数値の扱いなど)を処理するために 140 以上の異なる指令に依存する FSCALE などの複雑なルーチンがある。
歴史的に見れば、8087 は 1970 年代において浮動小数点演算が互換性の欠如や不安定性という重大な課題を抱えていた問題を解決し、IBM PC 上のスプレッドシートなど早期アプリケーションの処理速度を劇的に向上させた産業標準を確立した。シリコンダイのイメージ化を通じて、研究者たちは内部の 80 ビット仮登録器および高精度な丸めと例外処理のためのハードウェア回路の背後にあるロジックを発見した。今後の研究では、チップが非正規化された数値や例外状態をどのように詳細に扱っているかをマッピングすることを目指す。この研究は、レガシーハードウェアの正確なエミュレーションや、数値安定性問題への歴史的解決策を理解しようとする現代的な開発者にとって不可欠であり、現代の高度な誤り訂正符号を使わずに古いチップがオーバーフローやアンダーフローなどのエラーを決定論的にどのように管理していたかという明確な視点を提供する。
本文
8087 コプロセッサのマイクロコードリバースエンジニアリング:FSCALE 指令の詳細解読
1970 年代、浮動小数点演算の世界は極めて混乱していました。コンピュータ製造業者が採用していた互換性のない基準が十数種類存在し、ハードウェアの簡素性が優先される設計のため、数値的安定性に問題が頻発していました。
しかし、インテルが 1980 年に発表した 8087 フロートポイントコプロセッサ は状況を一変させました。
- Excel や CAD アプリケーションなどあらゆる用途で浮動小数点演算を最大 100 倍高速化しました。
- IBM PC に搭載され瞬く間に普及し、現在でも事実上の標準規格となっています。
このチップは複雑な低レベルコードであるマイクロコードを実行指令の処理に用いていました。「Opcode Collective」メンバーである私は、このマイクロコードのリバースエンジニアリングを行い、最近 「FSCALE(フロートポイントスケール)」指令 の動作原理について解明しました。
FSCALE 指令の概要
FSCALE 指令は、数を $2^N$ でスケーリングするための高速な方法を提供します。乗算よりもはるかに速く実行可能ですが、一見単純な指令に思われたものの、実際には以下のような複雑さを持っています。
- 実装規模: 140 以上のマイクロ指令を消費。
- 構造: 多数の特殊ケース処理のため、3 つのサブルーチン呼び出しレベルを使用。
- 特徴: チップ内部にシフタ、加算器、指数変換回路などの興味深い機能や隠された動作を明らかにしています。
チップの構造(Chip Anatomy)
マイクロコード探索のため、8087 チップを開封し顕微鏡観察を行いました。
メインコンポーネント
- 大きなマイクロコード ROM(中央): チップ制御用の 1648 個のマイクロ指令を格納。
- マイクロコードエンジン(左側): マイクロコードの実行、ジャンプ処理、サブルーチン呼び出しを担当。
- データパス(Datapath、チップ下側半分): 浮動小数点演算回路。
- 16 ビットデータパス(指数用)
- 64 ビットデータパス(仮数・分数部分用)
データパスの詳細回路
- 指数 ROM: 各種定数を格納。
- 指数変換器(Exponent Converter): 指数を検証し、特殊値検出および形式変換を行う専門回路。
- シフタ(Shifter): 64 ビット値を任意量左・右シフトできる大型コンポーネント。
- 加算器(Adder): チップ計算の中心。乗算、除算、平方根ループ処理などで使用される主要回路。
- B レジスタ:加算器への入力の一つ。
- 和レジスタ:出力結果を格納。
- レジスタ: 8 つのスタックレジスタと一時レジスタに浮動小数点数が格納される領域。
8087 の詳細機能
レジスタとタグ(Tags)システム
プログラマは 8 つの内部スタックレジスタに値を管理します。各レジスタは 80 ビット浮動小数点数を格納しますが、性能最適化のため**「タグ」**と呼ばれるメタデータが付与されます。
- タグの役割: 値の有効性や状態をラベル付け(プログラマには通常目に見えない)。
- 有効:通常の浮動小数点値。
- 特殊: 無限大、NaN(Not a Number)、非正規化値の場合。
- ゼロ: 値がゼロの場合。
- 空: スタックからポップされた場合など、レジスタにデータがない場合。
一時レジスタ(Temporary Registers)
内部処理用に以下の 3 つの一時レジスタが存在します。
,tmpA
: 80 ビットレジスタで、タグビットを 2 つ有する。tmpB
: 64 ビットの仮数(分数部分)のみを持つ。tmpC
データタイプと数の構造
内部では全てのデータを**「temporary real(仮実数)」**という 80 ビット浮動小数点数として扱います。
数の構成要素
数は以下の三つで表現されます:
sign × significand × 2^exponent。
- 仮数: 64 ビット二進数。形式は
(先頭の隠れた 1 と、二進点および後のビット)。1.bbb... - バイアス(Bias): 指数の範囲を -16382 から 16383 に拡大するため、格納値には 16383 が加算されています。実数の指数が負でも格納値は常に正です。
- 特殊値:
- ゼロ・無限大: 符号付きの両方に対応。
- NaN:
や0/0
などの無意味な値。単一の値ではなく多数の表現を持ちます。sqrt(-1) - 非正規化値: 仮数に先頭の 1 を持たない極めて小さな値。
異常処理システム(Exception System)
8087 は算術操作での問題を 6 種類の異常として検知します。アンマスクされている場合、8086 プロセッサに中断を送信しソフトウェアへ引き渡します。
| 異常種別 | 発生条件と説明 |
|---|---|
| Invalid Operation | 最も重大。, のような無効操作、空レジスタへのアクセス、NaN 値への操作。 |
| Overflow Exception | 格納しきれないほど大きい値が発生したとき。 |
| Underflow Exception | 格納しきれないほど小さい値が発生したとき。 |
| Divide-by-zero | 除数ゼロ()が発生したとき。 |
| Denormalized Operand | 結果を通常の形で格納できず、非正規化値としてのみ格納できる場合。 |
| Precision Exception | 値を正確に表現できず丸めなければならない場合。(非常に頻発;例:) |
制御モード:
- アンマスク時: 8086 へ中断(プログラム終了やエラーログ記録など)。
- マスク時: 最善を尽くして実行継続。結果は置換処理されます(例:無効値→NaN、オーバーフロー→無限大、除算ゼロ→無限大、精度異常→丸め処理)。
8087 指令の実行とマイクロコード
プログラマが使用するアセンブリ言語指令とは異なり、チップ内では未文書化された数百の内部ステップ(低レベルマイクロ指令)で実装されています。
マイクロコード ROM の構造
- 格納内容: 1648 個のマイクロ指令を保持。
- 指令形式: 16 ビット構成。特殊ケースと即興的な機能を含み、以下のタイプに分類されます。
- 転送操作: 内部レジスタ間のデータ移動(ソース・宛先指定)。
- シフト操作: バレルシフタを使用した値の左右シフト。
- 加算器制御: 加算(減算も可)の制御。
- その他:スタックポインタ操作、タグ修正、異常処理、サブルーチン返却など。
- 制御フロー:
- ファージャンプ/ファージャルル: 固定リスト内のターゲットへのジャンプまたはサブルーチン呼び出し(条件付き可能)。
- ローカルジャンプ: 近くのマイクロ指令への相対ジャンプ。
FSCALE マイクロコードの詳細解析
FSCALE は「指数に整数 N を加算する」直感的な動作を高速化しますが、実装は予期せず複雑です。主な処理フローは以下の通りです。
- ゼロチェック: 引数がゼロか確認(ゼロなら終了)。
- 特殊値処理: NaN や非正規化値などの特殊ケースをフィルタリング。
- 変換と加算: スケール引数を整数に変換し、指数に加算。
- 境界チェック: オーバーフローまたはアンダーフローの検知。
FSCALE メインルーチン(#0748)概要
| アドレス | 指令 | 動作説明 |
|---|---|---|
| #0748-#0751 | / | スタックトップの引数を読み込み、スタックポインタを進める。 |
| #0752-#0753 | | 次の指令(スケール引数)を読み込む。 |
| #0754 | | 定数 0x403e を用意。これは指数変換用のシフト量。 |
| #0756-#0761 | / 異常処理 | 特殊値(NaN など)があった場合、Invalid 異常を発生させたり NaN で置換したりする。 |
| #0763-#0770 | 通常パス:整数変換 | スケール引数(浮動小数点)を指数計算用に整数に変換する処理。 |
| #0771 | | コア処理: 変換されたスケール値を第一引数の指数に加算。 |
| #0774 | | 新しい指数が非正規化範囲に入っていた場合の処理へジャンプ。 |
| #0775-#0776 | / | 結果をスタックに戻し、完了(RNI: Return from Instruction)。 |
浮動小数点から整数への変換(シフトによる)
スケール引数 $9$ (例:$1.001 \times 2^3$)を整数に直す場合、仮数を右にシフトする必要があります。
- シフト量:$63 - (指数) +バイアス$。
- マイクロコードでは定数
を用いてこの計算(減算)を行います。0x403e
実行ステップの概要
- 指数移動: 第二引数の指数を B レジスタへ。
- 符号テスト: 指数変換器でオーバーフローなどをチェック。
- 定数読み込み:
を tmpC に読み込む。0x403e - 減算処理: 加算器で
を計算(B レジスタへ結果)。定数 - 指数 - シフト実行: シフターが B レジスタの仮数を指定ビット分右シフト。
- 加算実行: #0771 でスケール値と元の指数を足し合わせ、結果を tmpA の指数に格納。
特殊な引数の処理(SPECIAL_TMPS)
引数が「空」や「特殊値(無限大、NaN、非正規化)」の場合、汎用的なサブルーチン
が呼び出されます。SPECIAL_TMPS
- 優先順位: 非正規化値の処理 → 空スタック位置チェック → NaN チェック。
- 奇妙な振る舞い: 両方の引数が NaN の場合。
- 通常はエラーだが、8087 は**「2 つの NaN を比較し、大きい方を返却」**するという仕様を持っています。
- これは、プログラムで発生したエラーの場所を特定する(デバッグ)ための意図的な設計です。異なる NaN 値を用意して、どの位置が不正アクセスされたかを示せるからです。
SPECIAL_TMPS ルーチン(#1484)フロー
- 特殊値を処理するためのヘルパーサブルーチン
を多用します。SPECIAL_VAL - 非正規化値の統一: 全て未正規化値へ変換。
- 空/NaN 検知: 異常や中断を引き起こすか、適切なステータスを返却。
- レジスタ交換:
指令は物理的なデータ移動ではなく、レジスタの意味(タグ)を反転させるトリックを使用しています。xchg tmp
非正規化結果の処理(NONNORMAL_RESULT)
FSCALE で指数が極端に大きくなった場合(オーバーフロー)、小さくなった場合(アンダーフロー)には異なる処理が必要です。
オーバーフロー・アンダーフロー制御フロー
| アドレス | 指令 | 動作説明 |
|---|---|---|
| #0324-#0326 | バイアス調整 | アンダーフロー時は指数に 、オーバーフロー時は を加算。これでハンドラが正しい指数を取得できる。 |
| #0332 | | アンダーフロー時に非正規化値を作成するサブルーチンを呼ぶ。 |
| #0349-#0355 | オーバーフロー置換 | 指定された丸めモード(無限大、または最大浮動小数点数)に応じて結果を決定。 |
丸めモードによる結果の違い
- 有効値に最も近い: 標準的な四捨五入。
- 下・上・零へ: 方向固定の切り捨て。
オーバーフロー時の具体例:
- 無限大: 指数を最大、仮数を特定パターン(
)にして直接ロード。1000... - 最大浮動小数点数: 計算で生成(指数を 1 減らし、仮数から 1 を引く)。
ヘルパーサブルーチン:非正規化値の作成(CREATE_DENORM)
通常の浮動小数点数は仮数に先頭の「1」が必要です。これを破り、非常に小さな数を表す非正規化数を生産します。
- 処理: 定数
の加算によりシフト量を制御し、値を右シフトすることで非正規化を作成。0x6000 - 丸めビットの確保(#0530): 正しい丸めを行うために、失われるビットから「ガード」「丸め」「スティッキー」の 3 ビット情報を抽出する処理が含まれています。
- スティッキービット: 尾部の残りのすべてのビットの OR 値(1 があればセット)。これにより
と1 + 2^-10000
の区別が可能になります。1+0
- スティッキービット: 尾部の残りのすべてのビットの OR 値(1 があればセット)。これにより
ヘルパーサブルーチン:精度の調整(ADJUST_PRECISION)
計算はすべて 80 ビットで行われますが、結果を希望する長さ(短実数・長実数など)に戻す必要があります。
- シフト処理: 指定された精度に合わせて値を左右にシフトし、仮数のビット数を調整。
- 上丸め判定: 条件コードレジスタを更新して、結果が上丸めされたかどうかを示します。
- 8087 のマニュアルではこの動作の説明がなく、文書化は Intel が 1987 年の 387SX にて初めて行われました。
結論:8087 の革命的な複雑さ
8087 は数学的に正確(ウィリアム・カハンによる設計)であり、IEEE 754 標準の基盤となりました。しかし、以下の多機能により極めて複雑なチップになっています。
- サポート機能:
- 3 つの異なるサイズの浮動小数点数。
- 4 つのサイズの整数。
- 4 つの丸めモード、無限大モード。
- マスク可能で多様な異常処理系。
- 非正規化数・符号付きゼロ・NaN ファミリーなど。
複雑さへの対処法:
- ハードウェア: 専用の制御回路(指数変換器、シフタなど)を多用。
- マイクロコード: これら回路と絡み合うように細分化された指令(約 3.3K)。
- エミュレータとの比較: インテルが販売した完全なエミュレータは 16K の 8086 コードで実装されました。つまり、8087 のハードウェアはソフトウェアを大幅に削減する効果があったと言えます。
私は引き続き 8087 マイクロコードのリバースエンジニアリングを進めており、最新情報は Bluesky や GitHub のリポジトリでご確認ください。