Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 13784] ファイルの概要

このコミットは、Go言語のリンカである6l (amd64アーキテクチャ用) および 8l (386アーキテクチャ用) における浮動小数点演算命令のエンコーディングに関する以前の変更 (CL 6498092 / 4ff71bc1a199) を元に戻すものです。具体的には、FSUBD, FSUBRD, FDIVD, FDIVRD といった浮動小数点減算および除算命令が、目的レジスタがF0 (FPUスタックトップ) でない場合に誤ったオペコードを生成するという問題に対する修正が、386アーキテクチャ上でテストを壊したため、その修正自体が取り消されました。

コミット

commit 2c5b53866c001632678c0467f713d019cf50c1a6
Author: Adam Langley <agl@golang.org>
Date:   Mon Sep 10 15:52:36 2012 -0400

    undo CL 6498092 / 4ff71bc1a199
    
    Broke tests on 386.
    
    ««« original CL description
    6l/8l: emit correct opcodes to F(SUB|DIV)R?D.
    
    When the destination was not F0, 6l and 8l swapped FSUBD/FSUBRD and
    FDIVD/FDIVRD.
    
    R=golang-dev, dave, rsc
    CC=golang-dev
    https://golang.org/cl/6498092
    »»»
    
    R=golang-dev
    CC=golang-dev
    https://golang.org/cl/6492100

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/2c5b53866c001632678c0467f713d019cf50c1a6

元コミット内容

このコミットは、以前のコミット 4ff71bc1a199 (CL 6498092) を元に戻すものです。元コミットの目的は、Go言語のリンカである6l (AMD64アーキテクチャ用) と 8l (386アーキテクチャ用) が、浮動小数点減算 (FSUBD, FSUBRD) および除算 (FDIVD, FDIVRD) 命令に対して誤ったオペコードを生成するバグを修正することでした。

具体的には、これらの命令で演算結果の格納先がFPUスタックトップレジスタ F0 (または ST(0)) でない場合、FSUBDFSUBRD、および FDIVDFDIVRD のオペコードが入れ替わってしまうという問題がありました。FSUBDST(0) - ST(i) を計算し結果を ST(0) に格納するのに対し、FSUBRDST(i) - ST(0) を計算し結果を ST(0) に格納します。同様に FDIVDFDIVRD もオペランドの順序が逆になります。このオペコードの誤った選択は、浮動小数点演算の正確性に直接影響を与える重大なバグでした。

元コミットは、この問題を解決するために src/cmd/6l/optab.csrc/cmd/8l/optab.c 内の命令定義テーブルを修正し、正しいオペコードが生成されるように変更しました。また、この修正を検証するためのテストケース test/fixedbugs/bug453.dir/ も追加されていました。

変更の背景

このコミットが行われた背景には、元コミット 4ff71bc1a199 が386アーキテクチャ上でテストを壊したという事実があります。元コミットは、浮動小数点演算のオペコード生成のバグを修正することを目的としていましたが、その修正が意図しない副作用として、特定の環境(この場合は386アーキテクチャ)での既存のテストを失敗させてしまいました。

ソフトウェア開発において、バグ修正が新たなバグを生み出したり、既存の機能を破壊したりすることは珍しくありません。特に、低レベルのコード生成やアーキテクチャ固有の最適化に関わる変更は、異なるプラットフォームやコンフィギュレーションで予期せぬ挙動を引き起こす可能性があります。

この場合、386アーキテクチャでのテスト失敗は、元コミットの修正が386環境の特定の条件下で正しく機能しないか、あるいは別の問題を引き起こしたことを示唆しています。安定性を最優先するため、問題の原因を特定して修正するよりも、一旦元の状態に戻す(undoする)という判断が下されました。これは、問題のある変更を一時的に取り消し、より安全な状態に戻すことで、開発の進行を妨げないようにするための一般的なプラクティスです。

前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

  1. Go言語のツールチェイン:

    • 6l (Go Linker for AMD64): Go言語のコンパイラが出力したオブジェクトファイルをリンクし、実行可能ファイルを生成するツールです。l は "linker" を意味します。6 はAMD64 (x86-64) アーキテクチャを指すGo言語の慣習的なプレフィックスです。
    • 8l (Go Linker for 386): 同様に、386 (x86 32-bit) アーキテクチャ用のリンカです。8 は386アーキテクチャを指します。
    • これらのリンカは、アセンブリコードを機械語に変換するアセンブラの機能も一部含んでいます。
  2. x86浮動小数点演算 (x87 FPU):

    • x86アーキテクチャには、浮動小数点演算を行うための専用のコプロセッサであるx87 FPU (Floating-Point Unit) があります。
    • x87 FPUは、スタックベースのレジスタセット (ST(0) から ST(7)) を使用します。ST(0) は常にスタックのトップを指します。
    • 浮動小数点命令:
      • FSUBD: FPUスタックトップ (ST(0)) から指定されたレジスタ (ST(i)) の値を減算し、結果を ST(0) に格納します。(ST(0) = ST(0) - ST(i))
      • FSUBRD: 指定されたレジスタ (ST(i)) からFPUスタックトップ (ST(0)) の値を減算し、結果を ST(0) に格納します。(ST(0) = ST(i) - ST(0))
      • FDIVD: FPUスタックトップ (ST(0)) を指定されたレジスタ (ST(i)) の値で除算し、結果を ST(0) に格納します。(ST(0) = ST(0) / ST(i))
      • FDIVRD: 指定されたレジスタ (ST(i)) をFPUスタックトップ (ST(0)) の値で除算し、結果を ST(0) に格納します。(ST(0) = ST(i) / ST(0))
    • これらの命令は、オペランドの順序によって異なるオペコードを持ちます。リンカ/アセンブラは、アセンブリコードの記述に基づいて正しいオペコードを選択する必要があります。
  3. optab.c:

    • Go言語のリンカ (6l, 8l) のソースコードに含まれるファイルで、アセンブリ命令とそれに対応する機械語オペコードの定義テーブル (Optab 構造体の配列) を含んでいます。
    • このテーブルは、リンカがアセンブリ命令を解析し、適切な機械語命令に変換する際に参照されます。各エントリは、命令の種類、オペランドの型、オペコード、およびその他のエンコーディング情報を含みます。
  4. CL (Change List):

    • Goプロジェクトでコード変更を管理するために使用される用語です。通常、Gerritなどのコードレビューシステムで管理されます。CL 6498092 は特定の変更セットを指します。

技術的詳細

このコミットの技術的詳細は、Go言語のリンカがx86浮動小数点命令のオペコードをどのように選択し、エンコードするかという点に集約されます。

src/cmd/6l/optab.c および src/cmd/8l/optab.c 内の Optab 構造体は、各アセンブリ命令の特性を定義しています。この構造体には、命令の種類 (AFSUBD, AFSUBRD など)、オペランドの型、そして最も重要なオペコードバイト列と、そのオペコードに付随する追加情報(例えば、ModR/MバイトのModフィールドやRegフィールド、またはSIBバイトの一部として使用される値)が含まれます。

元コミット 4ff71bc1a199 は、AFSUBD, AFSUBRD, AFDIVD, AFDIVRD 命令の Optab エントリの最後のパラメータを変更していました。この最後のパラメータは、オペコードのバリアントや、ModR/Mバイトの特定のビットフィールド(例えば、Regフィールド)を決定するために使用される値であると推測されます。x86命令セットでは、同じ命令でもオペランドの型やレジスタの指定方法によって異なるオペコードやModR/Mバイトのエンコーディングを持つことがよくあります。

元コミットの意図は、目的レジスタが F0 でない場合に FSUBD/FSUBRD および FDIVD/FDIVRD のオペコードが誤って入れ替わる問題を修正することでした。これは、リンカが命令を処理する際に、オペランドの順序や目的レジスタの指定に応じて、正しいオペコードバリアントを選択できていなかったことを意味します。例えば、FSUBD ST(i), ST(0)FSUBRD ST(i), ST(0) は、アセンブリレベルでは似て見えても、機械語レベルでは異なるオペコードを持ちます。リンカは、これらの微妙な違いを正確に反映するオペコードを生成する必要があります。

しかし、この修正が386アーキテクチャでテストを壊したということは、以下のいずれかの問題が発生した可能性が高いです。

  1. 386アーキテクチャ固有のエンコーディングの違い: AMD64と386では、同じ命令でもエンコーディングの細部が異なる場合があります。元コミットの修正がAMD64では正しくても、386では別の問題を引き起こした可能性があります。
  2. テストケースの不備: 元コミットのテストケース bug453.dir が、386アーキテクチャの特定のコーナーケースを十分にカバーしていなかったか、あるいは386環境での挙動を誤解していた可能性があります。
  3. リンカの他の部分との相互作用: 修正が optab.c の命令定義に限定されていても、リンカの他の部分(例えば、レジスタ割り当て、最適化パス)との相互作用によって、386環境で予期せぬ結果が生じた可能性があります。

このコミットは、これらの問題を深掘りして修正するのではなく、一旦元に戻すことで、Go言語のツールチェインの安定性を優先しました。これは、複雑な低レベルのバグ修正においては、段階的なアプローチや、より広範なテストカバレッジが重要であることを示唆しています。

コアとなるコードの変更箇所

このコミットでは、以下の2つのファイルが変更され、複数のテストファイルが削除されています。

  1. src/cmd/6l/optab.c: AMD64リンカの命令定義テーブル
  2. src/cmd/8l/optab.c: 386リンカの命令定義テーブル

これらのファイルでは、AFSUBD, AFSUBRD, AFDIVD, AFDIVRD 命令に対応する Optab エントリの最後の数値が元に戻されています。

src/cmd/6l/optab.c および src/cmd/8l/optab.c の変更点 (共通):

--- a/src/cmd/6l/optab.c
+++ b/src/cmd/6l/optab.c
@@ -1199,25 +1199,25 @@ Optab optab[] =
  	{ AFSUBW,	yfmvx,	Px, 0xde,(04) },
  	{ AFSUBL,	yfmvx,	Px, 0xda,(04) },
  	{ AFSUBF,	yfmvx,	Px, 0xd8,(04) },
- 	{ AFSUBD,	yfadd,	Px, 0xdc,(04),0xd8,(04),0xdc,(04) },
+ 	{ AFSUBD,	yfadd,	Px, 0xdc,(04),0xd8,(04),0xdc,(05) },
 
  	{ AFSUBRDP,	yfaddp,	Px, 0xde,(04) },
  	{ AFSUBRW,	yfmvx,	Px, 0xde,(05) },
  	{ AFSUBRL,	yfmvx,	Px, 0xda,(05) },
  	{ AFSUBRF,	yfmvx,	Px, 0xd8,(05) },
- 	{ AFSUBRD,	yfadd,	Px, 0xdc,(05),0xd8,(05),0xdc,(05) },
+ 	{ AFSUBRD,	yfadd,	Px, 0xdc,(05),0xd8,(05),0xdc,(04) },
 
  	{ AFDIVDP,	yfaddp,	Px, 0xde,(07) },
  	{ AFDIVW,	yfmvx,	Px, 0xde,(06) },
  	{ AFDIVL,	yfmvx,	Px, 0xda,(06) },
  	{ AFDIVF,	yfmvx,	Px, 0xd8,(06) },
- 	{ AFDIVD,	yfadd,	Px, 0xdc,(06),0xd8,(06),0xdc,(06) },
+ 	{ AFDIVD,	yfadd,	Px, 0xdc,(06),0xd8,(06),0xdc,(07) },
 
  	{ AFDIVRDP,	yfaddp,	Px, 0xde,(06) },
  	{ AFDIVRW,	yfmvx,	Px, 0xde,(07) },
  	{ AFDIVRL,	yfmvx,	Px, 0xda,(07) },
  	{ AFDIVRF,	yfmvx,	Px, 0xd8,(07) },
- 	{ AFDIVRD,	yfadd,	Px, 0xdc,(07),0xd8,(07),0xdc,(07) },
+ 	{ AFDIVRD,	yfadd,	Px, 0xdc,(07),0xd8,(07),0xdc,(06) },
 
  	{ AFXCHD,	yfxch,	Px, 0xd9,(01),0xd9,(01) },
  	{ AFFREE },

削除されたテストファイル:

  • test/fixedbugs/bug453.dir/bug453.go
  • test/fixedbugs/bug453.dir/bug453.s
  • test/fixedbugs/bug453.dir/bug453_ref.go
  • test/fixedbugs/bug453.go

これらのテストファイルは、元コミットで追加されたもので、浮動小数点演算のオペコード生成の修正を検証するためのものでした。修正が元に戻されたため、これらのテストも削除されました。

コアとなるコードの解説

optab.c ファイル内の Optab 構造体の配列は、Go言語のリンカがアセンブリ命令を機械語に変換する際の「辞書」のような役割を果たします。各行は特定のアセンブリ命令の定義であり、その命令がどのようにエンコードされるかを示しています。

変更された行は以下の形式を持っています(簡略化): { <命令名>, <オペランドタイプ>, <プレフィックス>, <オペコード1>, (<追加バイト1>), <オペコード2>, (<追加バイト2>), ... }

ここで注目すべきは、AFSUBD, AFSUBRD, AFDIVD, AFDIVRD の行の最後の数値です。

  • AFSUBD:

    • 変更前: ... 0xdc,(04)
    • 変更後: ... 0xdc,(05)
    • AFSUBDST(0) = ST(0) - ST(i) を意味します。元コミットはこれを (04) に変更しましたが、このコミットで (05) に戻されました。
  • AFSUBRD:

    • 変更前: ... 0xdc,(05)
    • 変更後: ... 0xdc,(04)
    • AFSUBRDST(0) = ST(i) - ST(0) を意味します。元コミットはこれを (05) に変更しましたが、このコミットで (04) に戻されました。
  • AFDIVD:

    • 変更前: ... 0xdc,(06)
    • 変更後: ... 0xdc,(07)
    • AFDIVDST(0) = ST(0) / ST(i) を意味します。元コミットはこれを (06) に変更しましたが、このコミットで (07) に戻されました。
  • AFDIVRD:

    • 変更前: ... 0xdc,(07)
    • 変更後: ... 0xdc,(06)
    • AFDIVRDST(0) = ST(i) / ST(0) を意味します。元コミットはこれを (07) に変更しましたが、このコミットで (06) に戻されました。

これらの数値 (04), (05), (06), (07) は、x86命令のModR/MバイトのRegフィールドまたはOpcode Extensionフィールドに対応している可能性が高いです。x86の浮動小数点命令では、同じオペコードプレフィックス (0xdc など) を持ちながら、ModR/Mバイトの特定のビット(通常はRegフィールド)によって、具体的な演算(減算か逆減算か、除算か逆除算か)が区別されます。

元コミットは、これらのフィールドの値を入れ替えることで、FSUBDFSUBRDFDIVDFDIVRD のオペコード生成を修正しようとしました。しかし、この「修正」が386アーキテクチャで問題を引き起こしたため、このコミットでは元の(問題があったとされる)値に戻すことで、386でのテスト失敗を回避しました。

これは、Go言語のリンカが、異なるアーキテクチャ(AMD64と386)間で浮動小数点命令のエンコーディングの微妙な違いをどのように扱うか、あるいは過去のバグがどのように再発しうるかを示す良い例です。低レベルのコード生成におけるこのような変更は、非常にデリケートであり、広範なテストと検証が不可欠であることを強調しています。

関連リンク

参考にした情報源リンク

  • x86 Instruction Set Reference: Intel 64 and IA-32 Architectures Software Developer's Manuals (特にVolume 2A: Instruction Set Reference, A-M および Volume 2B: Instruction Set Reference, N-Z のFPU命令に関する章)
  • Go言語のリンカのソースコード: src/cmd/6l/optab.c, src/cmd/8l/optab.c (Go言語のリポジトリ内)
  • Go言語のIssue Tracker: 関連するバグ報告や議論がある場合、GoのIssue Tracker (https://github.com/golang/go/issues) を参照。
  • Go言語のコードレビューシステム: Gerrit (https://go-review.googlesource.com/) - CL番号で検索可能。