[インデックス 17170] ファイルの概要
このコミットは、Goコンパイラのx86アーキテクチャ向けバックエンド(cmd/6g
は32-bit x86、cmd/8g
は64-bit x86)における命令情報(ProgInfo
)の定義に関するバグ修正です。具体的には、特定の算術・論理命令がCPUのキャリーフラグ(Carry Flag, CF)に影響を与えることをProgInfo
が正しく反映していなかった点を修正しています。
コミット
commit 9a0a59f171d0ae1defff35440c93e1a119289c29
Author: Russ Cox <rsc@golang.org>
Date: Mon Aug 12 21:02:55 2013 -0400
cmd/6g, cmd/8g: proginfo carry fixes
Bugs pointed out by cshapiro in CL 12637051.
R=cshapiro
CC=golang-dev
https://golang.org/cl/12815043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9a0a59f171d0ae1defff35440c93e1a119289c29
元コミット内容
cmd/6g, cmd/8g: proginfo carry fixes
Bugs pointed out by cshapiro in CL 12637051.
このコミットは、Goコンパイラのx86(32ビットおよび64ビット)バックエンドにおいて、命令のプロパティを定義するProgInfo
構造体に関するキャリーフラグの取り扱いにおけるバグを修正するものです。このバグは、cshapiro
によって指摘されたCL 12637051
に関連しています。
変更の背景
Goコンパイラは、ソースコードを機械語に変換する過程で、各命令がどのような副作用を持つか(例えば、どのレジスタを読み書きするか、どのフラグに影響を与えるかなど)を正確に把握する必要があります。これは、命令のスケジューリング、最適化、および正確なコード生成のために不可欠です。
このコミットの背景には、Goコンパイラのx86バックエンド(cmd/6g
とcmd/8g
)が、特定の命令(特に除算命令やテスト命令)がCPUのキャリーフラグ(Carry Flag, CF)に影響を与えるという事実を、その命令のProgInfo
定義に含めていなかったという問題があります。
ProgInfo
は、各アセンブリ命令の特性を記述するデータ構造で、コンパイラが命令を処理する際に参照します。例えば、LeftRead
は左オペランドを読み取ることを示し、RightRdwr
は右オペランドを読み書きすることを示します。しかし、一部の命令、特にDIV
(除算)やTEST
(論理積テスト)命令は、演算結果に応じてキャリーフラグを含むCPUのフラグレジスタを更新します。この情報がProgInfo
に欠けていると、コンパイラがフラグの状態を誤って推測し、結果として不正な最適化を行ったり、誤ったコードを生成したりする可能性がありました。
CL 12637051
("cmd/6g, cmd/8g: fix proginfo for TESTB, TESTL, TESTW")は、TEST
命令に関する同様のProgInfo
の修正を試みたものですが、このコミット(CL 12815043
)は、その修正が不完全であったこと、または他の関連する命令にも同様の問題があることをcshapiro
が指摘したことによって行われました。具体的には、除算命令(DIV
、IDIV
)もキャリーフラグに影響を与えるため、それらにもSetCarry
フラグを追加する必要がありました。
この修正は、コンパイラが生成するコードの正確性と堅牢性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の概念が重要です。
-
Goコンパイラバックエンド (
cmd/6g
,cmd/8g
):- Go言語のコンパイラは、フロントエンドでGoソースコードを中間表現に変換し、バックエンドでその中間表現を特定のCPUアーキテクチャの機械語に変換します。
cmd/6g
は、32ビットx86アーキテクチャ(Intel/AMD互換プロセッサ)向けのGoコンパイラバックエンドです。cmd/8g
は、64ビットx86アーキテクチャ(x86-64またはAMD64)向けのGoコンパイラバックエンドです。- これらのバックエンドは、アセンブリ命令の生成と最適化を担当します。
-
ProgInfo
構造体:- Goコンパイラのバックエンドでは、各アセンブリ命令(
Prog
)が持つ特性を記述するためにProgInfo
というデータ構造が使用されます。 ProgInfo
は、その命令がどのレジスタを読み取るか(LeftRead
)、どのレジスタを書き込むか(RightRdwr
)、どのフラグに影響を与えるか(SetCarry
など)といった情報をビットフラグの形式で保持します。- この情報は、コンパイラが命令の依存関係を解析し、レジスタ割り当てを行い、コードを最適化する上で非常に重要です。
- Goコンパイラのバックエンドでは、各アセンブリ命令(
-
CPUフラグレジスタとキャリーフラグ (Carry Flag, CF):
- x86アーキテクチャのCPUには、演算結果の状態を示すための「フラグレジスタ」(EFLAGS/RFLAGS)があります。
- このフラグレジスタには、様々なビットフラグが含まれており、それぞれが特定の演算結果やCPUの状態を示します。
- キャリーフラグ (CF): 最も重要なフラグの一つで、符号なし演算において最上位ビットからの桁上がり(加算の場合)または桁借り(減算の場合)が発生したことを示します。これは、多倍長整数演算などで利用されます。
- オーバーフローフラグ (OF): 符号付き演算においてオーバーフローが発生したことを示します。
- ゼロフラグ (ZF): 演算結果がゼロになったことを示します。
- サインフラグ (SF): 演算結果が負になったことを示します。
- パリティフラグ (PF): 演算結果の最下位バイトのセットされたビット数が偶数であることを示します。
-
x86アセンブリ命令:
DIV
(Divide): 符号なし除算命令。オペランドで指定された値を、AX/DX(またはEAX/EDX、RAX/RDX)レジスタのペアで保持されている値で除算します。結果として商と剰余が生成され、フラグレジスタ(特にCF, OF)が更新されます。IDIV
(Integer Divide): 符号付き除算命令。DIV
と同様に除算を行いますが、符号付き整数として扱います。こちらもフラグレジスタが更新されます。TEST
(Logical Compare): 論理積(AND)演算を実行し、結果を破棄しますが、フラグレジスタ(特にZF, SF, PF)を更新します。キャリーフラグとオーバーフローフラグは常にクリアされますが、この「クリアされる」という動作自体がフラグへの影響とみなされます。
技術的詳細
このコミットの技術的詳細は、Goコンパイラの命令記述における正確性の向上にあります。
Goコンパイラのバックエンドでは、src/cmd/6g/prog.c
とsrc/cmd/8g/prog.c
に、各アセンブリ命令に対応するProgInfo
のテーブル(progtable
)が定義されています。このテーブルは、命令の種類(ALAST
は命令の列挙型)ごとに、その命令が持つ特性をビットフラグの組み合わせで表現します。
修正前は、以下の命令のProgInfo
定義にSetCarry
フラグが欠けていました。
ADIVB
,ADIVL
,ADIVQ
,ADIVW
(符号なし除算命令)AIDIVB
,AIDIVL
,AIDIVQ
,AIDIVW
(符号付き除算命令)ATESTB
,ATESTL
,ATESTQ
,ATESTW
(論理積テスト命令)
これらの命令は、x86アーキテクチャの仕様上、実行後にCPUのフラグレジスタ、特にキャリーフラグ(CF)に影響を与えます。
- 除算命令 (
DIV
,IDIV
): これらの命令は、除算の結果に応じてキャリーフラグとオーバーフローフラグを更新します。例えば、除数がゼロの場合や、商がレジスタに収まらない場合にフラグが設定されることがあります。 - テスト命令 (
TEST
):TEST
命令は、2つのオペランドのビットごとの論理積を実行し、結果を破棄しますが、その結果に基づいてゼロフラグ、サインフラグ、パリティフラグを設定します。重要なのは、TEST
命令は常にキャリーフラグとオーバーフローフラグをクリアするという点です。この「クリアする」という動作も、フラグの状態を変更する影響とみなされ、ProgInfo
でSetCarry
として表現されるべきです。
SetCarry
フラグは、その命令がキャリーフラグに影響を与えることをコンパイラに伝えます。この情報が正しく設定されていないと、コンパイラは以下のような問題を引き起こす可能性があります。
- 誤った最適化: コンパイラが、キャリーフラグの状態に依存する後続の命令(例:
ADC
(Add with Carry) や条件分岐命令)を誤って最適化してしまう可能性があります。例えば、キャリーフラグが特定の状態であると誤って仮定し、実際にはそうでない場合に不正なコードを生成するかもしれません。 - レジスタ割り当ての非効率性: フラグの状態が不明確な場合、コンパイラはより保守的なレジスタ割り当て戦略を採用せざるを得なくなり、生成されるコードの効率が低下する可能性があります。
- デバッグの困難さ: 生成されたアセンブリコードの動作が、コンパイラの意図と異なる場合、デバッグが非常に困難になります。
このコミットは、これらの命令のProgInfo
定義にSetCarry
フラグを追加することで、コンパイラがキャリーフラグの状態変化を正確に追跡できるようにし、より堅牢で正確なコード生成を可能にしています。これは、コンパイラの命令セット記述の正確性を高めるための重要な修正です。
コアとなるコードの変更箇所
変更は、src/cmd/6g/prog.c
とsrc/cmd/8g/prog.c
の2つのファイルにわたります。
それぞれのファイルで、progtable
配列内の特定の命令のProgInfo
定義にSetCarry
フラグが追加されています。
src/cmd/6g/prog.c
の変更点:
--- a/src/cmd/6g/prog.c
+++ b/src/cmd/6g/prog.c
@@ -99,18 +99,18 @@ static ProgInfo progtable[ALAST] = {
[ADECQ]={SizeQ | RightRdwr},
[ADECW]={SizeW | RightRdwr},
- [ADIVB]={SizeB | LeftRead, AX, AX},
- [ADIVL]={SizeL | LeftRead, AX|DX, AX|DX},
- [ADIVQ]={SizeQ | LeftRead, AX|DX, AX|DX},
- [ADIVW]={SizeW | LeftRead, AX|DX, AX|DX},
+ [ADIVB]={SizeB | LeftRead | SetCarry, AX, AX},
+ [ADIVL]={SizeL | LeftRead | SetCarry, AX|DX, AX|DX},
+ [ADIVQ]={SizeQ | LeftRead | SetCarry, AX|DX, AX|DX},
+ [ADIVW]={SizeW | LeftRead | SetCarry, AX|DX, AX|DX},
[ADIVSD]={SizeD | LeftRead | RightRdwr},
[ADIVSS]={SizeF | LeftRead | RightRdwr},
- [AIDIVB]={SizeB | LeftRead, AX, AX},
- [AIDIVL]={SizeL | LeftRead, AX|DX, AX|DX},
- [AIDIVQ]={SizeQ | LeftRead, AX|DX, AX|DX},
- [AIDIVW]={SizeW | LeftRead, AX|DX, AX|DX},
+ [AIDIVB]={SizeB | LeftRead | SetCarry, AX, AX},
+ [AIDIVL]={SizeL | LeftRead | SetCarry, AX|DX, AX|DX},
+ [AIDIVQ]={SizeQ | LeftRead | SetCarry, AX|DX, AX|DX},
+ [AIDIVW]={SizeW | LeftRead | SetCarry, AX|DX, AX|DX},
[AIMULB]={SizeB | LeftRead | SetCarry, AX, AX},
[AIMULL]={SizeL | LeftRead | ImulAXDX | SetCarry},
@@ -262,10 +262,10 @@ static ProgInfo progtable[ALAST] = {
[ASUBSD]={SizeD | LeftRead | RightRdwr},
[ASUBSS]={SizeF | LeftRead | RightRdwr},
- [ATESTB]={SizeB | LeftRead | RightRead},
- [ATESTL]={SizeL | LeftRead | RightRead},
- [ATESTQ]={SizeQ | LeftRead | RightRead},
- [ATESTW]={SizeW | LeftRead | RightRead},
+ [ATESTB]={SizeB | LeftRead | RightRead | SetCarry},
+ [ATESTL]={SizeL | LeftRead | RightRead | SetCarry},
+ [ATESTQ]={SizeQ | LeftRead | RightRead | SetCarry},
+ [ATESTW]={SizeW | LeftRead | RightRead | SetCarry},
[AUCOMISD]={SizeD | LeftRead | RightRead},
[AUCOMISS]={SizeF | LeftRead | RightRead},
src/cmd/8g/prog.c
の変更点:
--- a/src/cmd/8g/prog.c
+++ b/src/cmd/8g/prog.c
@@ -87,9 +87,9 @@ static ProgInfo progtable[ALAST] = {
[ADECL]={SizeL | RightRdwr},
[ADECW]={SizeW | RightRdwr},
- [ADIVB]={SizeB | LeftRead, AX, AX},
- [ADIVL]={SizeL | LeftRead, AX|DX, AX|DX},
- [ADIVW]={SizeW | LeftRead, AX|DX, AX|DX},
+ [ADIVB]={SizeB | LeftRead | SetCarry, AX, AX},
+ [ADIVL]={SizeL | LeftRead | SetCarry, AX|DX, AX|DX},
+ [ADIVW]={SizeW | LeftRead | SetCarry, AX|DX, AX|DX},
[ADIVSD]={SizeD | LeftRead | RightRdwr},
[ADIVSS]={SizeF | LeftRead | RightRdwr},
@@ -137,9 +137,9 @@ static ProgInfo progtable[ALAST] = {
[AFMULDP]={SizeD | LeftAddr | RightRdwr},
[AFMULF]={SizeF | LeftAddr | RightRdwr},
- [AIDIVB]={SizeB | LeftRead, AX, AX},
- [AIDIVL]={SizeL | LeftRead, AX|DX, AX|DX},
- [AIDIVW]={SizeW | LeftRead, AX|DX, AX|DX},
+ [AIDIVB]={SizeB | LeftRead | SetCarry, AX, AX},
+ [AIDIVL]={SizeL | LeftRead | SetCarry, AX|DX, AX|DX},
+ [AIDIVW]={SizeW | LeftRead | SetCarry, AX|DX, AX|DX},
[AIMULB]={SizeB | LeftRead | SetCarry, AX, AX},
[AIMULL]={SizeL | LeftRead | ImulAXDX | SetCarry},
@@ -282,9 +282,9 @@ static ProgInfo progtable[ALAST] = {
[ASUBSD]={SizeD | LeftRead | RightRdwr},
[ASUBSS]={SizeF | LeftRead | RightRdwr},
- [ATESTB]={SizeB | LeftRead | RightRead},
- [ATESTL]={SizeL | LeftRead | RightRead},
- [ATESTW]={SizeW | LeftRead | RightRead},
+ [ATESTB]={SizeB | LeftRead | RightRead | SetCarry},
+ [ATESTL]={SizeL | LeftRead | RightRead | SetCarry},
+ [ATESTW]={SizeW | LeftRead | RightRead | SetCarry},
[AUCOMISD]={SizeD | LeftRead | RightRead},
[AUCOMISS]={SizeF | LeftRead | RightRead},
コアとなるコードの解説
上記の変更箇所では、progtable
という静的配列が修正されています。この配列は、Goコンパイラのx86バックエンドがサポートする各アセンブリ命令(ADIVB
, ADIVL
, ADIVQ
, ADIVW
, AIDIVB
, AIDIVL
, AIDIVQ
, AIDIVW
, ATESTB
, ATESTL
, ATESTQ
, ATESTW
など)に対応するProgInfo
構造体のインスタンスを格納しています。
ProgInfo
構造体は、命令の特性をビットフラグの組み合わせで表現します。
SizeB
,SizeL
,SizeQ
,SizeW
,SizeD
,SizeF
: 命令が操作するデータのサイズ(バイト、ロングワード、クアッドワード、ワード、ダブルワード、浮動小数点)を示します。LeftRead
: 命令が左オペランドを読み取ることを示します。RightRdwr
: 命令が右オペランドを読み書きすることを示します。AX
,DX
,AX|DX
: 命令が影響を与えるレジスタを示します。除算命令は、商と剰余をそれぞれAX/EAX/RAXとDX/EDX/RDXレジスタに格納するため、これらのレジスタが影響を受けます。SetCarry
: このコミットで追加された重要なフラグ。このフラグは、その命令がCPUのキャリーフラグ(CF)に影響を与えることをコンパイラに伝えます。
具体的には、ADIV*
(符号なし除算)、AIDIV*
(符号付き除算)、ATEST*
(論理積テスト)の各命令の定義に| SetCarry
が追加されています。これは、これらの命令が実行されると、キャリーフラグの状態が変更される(設定されるか、クリアされるか)ことをコンパイラが認識するようにするためのものです。
この修正により、コンパイラはこれらの命令の後に続くコードのフラグ依存性をより正確に解析できるようになり、結果としてより正確で効率的な機械語コードを生成できるようになります。例えば、キャリーフラグの状態に基づいて条件分岐を行う命令(例: JC
(Jump if Carry))がある場合、コンパイラはSetCarry
フラグの存在によって、その前の除算やテスト命令がキャリーフラグに影響を与えたことを考慮に入れることができます。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/
- このコミットのGerritチェンジリスト: https://golang.org/cl/12815043
- 関連するGerritチェンジリスト (CL 12637051): https://golang.org/cl/12637051
参考にした情報源リンク
- x86 Instruction Set Reference (Intel/AMD manuals): CPU命令がフラグに与える影響に関する詳細な情報源。
- Go Compiler Source Code (特に
src/cmd/6g/prog.c
,src/cmd/8g/prog.c
):ProgInfo
の定義と使用方法を理解するための直接的な情報源。 - Go Language Design Documents and Discussions: Goコンパイラの設計思想や特定の最適化に関する議論が、このような低レベルの修正の背景を理解するのに役立つ場合があります。
- Stack Overflow や技術ブログ: x86アセンブリのフラグやコンパイラの最適化に関する一般的な知識を得るために参照しました。
- Gerrit Code Review System: 関連するチェンジリストのコメントや議論は、バグの発見経緯や修正の意図を理解する上で非常に重要でした。