[インデックス 17169] ファイルの概要
このコミットは、Goコンパイラ(gc
)における最適化ロジックの「ポータブル化」をサポートするための変更です。具体的には、fixjmp
とnoreturn
という2つの最適化関連関数を、アーキテクチャ固有のコンパイラ(5g
, 6g
, 8g
)から共通のgc/popt.c
ファイルに移動し、共通化された最適化ロジックとして管理できるようにしています。これにより、コードの重複を減らし、保守性を向上させることを目的としています。
コミット
commit b3b87143f2b5da57fb87e06bab5af188c77e6bb8
Author: Russ Cox <rsc@golang.org>
Date: Mon Aug 12 19:14:02 2013 -0400
cmd/gc: support for "portable" optimization logic
Code in gc/popt.c is compiled as part of 5g, 6g, and 8g,
meaning it can use arch-specific headers but there's
just one copy of the code.
This is the same arrangement we use for the portable
code generation logic in gc/pgen.c.
Move fixjmp and noreturn there to get the ball rolling.
R=ken2
CC=golang-dev
https://golang.org/cl/12789043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b3b87143f2b5da57fb87e06bab5af188c77e6bb8
元コミット内容
cmd/gc: support for "portable" optimization logic
Code in gc/popt.c is compiled as part of 5g, 6g, and 8g,
meaning it can use arch-specific headers but there's
just one copy of the code.
This is the same arrangement we use for the portable
code generation logic in gc/pgen.c.
Move fixjmp and noreturn there to get the ball rolling.
R=ken2
CC=golang-dev
https://golang.org/cl/12789043
変更の背景
Goコンパイラは、当初、異なるアーキテクチャ(例: 5gはARM、6gはx86-64、8gはx86-32)ごとに独立したコードベースを持っていました。しかし、多くのコンパイラ最適化ロジックはアーキテクチャに依存しない共通のものです。このような共通ロジックが各アーキテクチャ固有のコンパイラコードベースに分散していると、コードの重複が生じ、バグ修正や機能追加の際に複数の場所を変更する必要があり、保守が困難になります。
このコミットの背景には、このような共通の最適化ロジックを中央集約し、より効率的な開発と保守を実現するという目的があります。コミットメッセージにもあるように、これは既にgc/pgen.c
で採用されていた「ポータブルなコード生成ロジック」の共通化と同様のアプローチです。fixjmp
(ジャンプ命令の最適化)とnoreturn
(戻らない関数の識別)は、その共通化の最初のステップとして選ばれました。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラの構造と最適化に関する基本的な知識が必要です。
- Goコンパイラ (
gc
): Go言語の公式コンパイラです。Go 1.5以降はGo自身で書かれていますが、このコミットが作成された2013年時点ではC言語で書かれていました。 5g
,6g
,8g
: これらはGoコンパイラのバックエンド(コード生成部分)を指します。5g
: ARMアーキテクチャ向け6g
: x86-64アーキテクチャ向け8g
: x86-32アーキテクチャ向け これらのコンパイラは、それぞれ特定のCPUアーキテクチャに特化した機械語コードを生成します。
Prog
構造体: Goコンパイラの内部で、アセンブリ命令を表現するためのデータ構造です。各Prog
インスタンスは、単一のアセンブリ命令とそのオペランド、リンク情報などを保持します。Reg
構造体: レジスタ割り当てや最適化の過程で、Prog
(命令)に関連付けられる情報を持つラッパー構造体です。- 最適化パス: コンパイラが生成するコードの性能を向上させるために、様々な変換や分析を行う段階のことです。
fixjmp
: ジャンプ命令の最適化を行う関数です。具体的には、不要なジャンプ(例:JMP L1; L1: JMP L2
のような二重ジャンプや、次の命令への無駄なジャンプ)を削除したり、ジャンプ先を直接のターゲットに解決したりします。これにより、生成されるコードのサイズを削減し、実行効率を向上させます。noreturn
: 特定の関数が呼び出し元に戻らない(例:panic
、exit
など)ことを識別する関数です。コンパイラは、戻らない関数が呼び出された後のコードが到達不能であることを認識し、その部分のコードを削除するなどの最適化を行うことができます。
gc/pgen.c
: 既に存在していた「ポータブルなコード生成ロジック」を格納するファイルです。アーキテクチャに依存しない共通のコード生成処理がここに集約されていました。Prog->regp
/Prog->opt
:Prog
構造体内のポインタで、最適化パスで使用される追加情報(Reg
構造体など)を指します。このコミットでは、regp
からopt
に名称が変更され、より汎用的な最適化情報へのポインタであることを示しています。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
gc/popt.c
の新規作成:src/cmd/gc/popt.c
という新しいファイルが作成されました。このファイルは、アーキテクチャに依存しない共通の最適化ロジックを格納するためのものです。- このファイルには、
fixjmp
とnoreturn
関数が、各アーキテクチャ固有のreg.c
ファイルから移動されました。 popt.c
は、5g
,6g
,8g
の各コンパイラによってコンパイルされるように設定されます。これにより、各コンパイラは共通の最適化ロジックを使用できるようになります。popt.c
は、gg.h
やopt.h
といったアーキテクチャ固有のヘッダファイルを含めることができますが、その内容は3つのバックエンドの共通部分に限定されます。
-
fixjmp
とnoreturn
関数の移動と共通化:- これまで
src/cmd/5g/reg.c
,src/cmd/6g/reg.c
,src/cmd/8g/reg.c
にそれぞれ存在していたfixjmp
とnoreturn
関数が、src/cmd/gc/popt.c
に移動されました。 - これにより、これらの最適化ロジックは一元的に管理され、各アーキテクチャで同じコードが使用されるようになります。
noreturn
関数は、panicindex
,panicslice
,throwinit
,panic
,panicwrap
といったGoランタイムの関数が呼び出し元に戻らないことを識別します。fixjmp
関数は、ジャンプ命令のチェインを解決し、到達不能なコード(主に不要なジャンプ命令)を削除します。これは、マーク&スイープアルゴリズムに似た方法で、到達可能な命令をマークし、到達不能な命令を削除することで実現されます。
- これまで
-
Prog
構造体のregp
フィールドからopt
フィールドへの変更:Prog
構造体内のregp
(register pointer)フィールドがopt
(optimizer passes)フィールドに名称変更されました。- これは、このポインタがレジスタ割り当て情報だけでなく、より広範な最適化パスで使用される情報(
Reg
構造体など)を指すことを明確にするためです。 - これに伴い、各アーキテクチャの
gg.h
、opt.h
、peep.c
、reg.c
ファイルで、regp
を使用していた箇所がopt
に更新されています。
-
ビルドシステム (
src/cmd/dist/build.c
) の更新:src/cmd/dist/build.c
が更新され、gc/popt.c
とgc/popt.h
が5g
,6g
,8g
の各コンパイラのビルドプロセスに含まれるように変更されました。これにより、新しい共通最適化ファイルが正しくコンパイルされ、各コンパイラにリンクされるようになります。
-
AJMP
とACALL
の定義:src/cmd/5g/opt.h
にAJMP
とACALL
という列挙型が追加されました。これらは、gc/popt.c
内でアーキテクチャ固有のジャンプ(AB
)とコール(ABL
)命令を汎用的に参照するために使用されます。これにより、popt.c
が各アーキテクチャの具体的な命令コードに依存することなく、共通のロジックを記述できるようになります。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。
src/cmd/gc/popt.c
(新規作成):noreturn
関数の実装。chasejmp
関数の実装(ジャンプチェインの解決)。mark
関数の実装(到達可能なコードのマーク)。fixjmp
関数の実装(不要なジャンプの削除とコードの到達可能性に基づく最適化)。
src/cmd/gc/popt.h
(新規作成):fixjmp
とnoreturn
関数のプロトタイプ宣言。
src/cmd/{5g,6g,8g}/reg.c
:fixjmp
とnoreturn
関数の削除。Prog
構造体のreg
またはregp
フィールドへの参照をopt
に変更。
src/cmd/{5g,6g,8g}/gg.h
:Prog
構造体内のregp
またはreg
フィールドをopt
に変更。
src/cmd/{5g,6g,8g}/opt.h
:../gc/popt.h
のインクルードを追加。noreturn
関数のプロトタイプ宣言を削除。Prog
構造体のregp
またはreg
フィールドへの参照をopt
に変更。5g/opt.h
にはAJMP
とACALL
の定義を追加。
src/cmd/{5g,6g,8g}/peep.c
:Prog
構造体のreg
またはregp
フィールドへの参照をopt
に変更。
src/cmd/{5g,6g,8g}/prog.c
:AUNDEF
とAUSEFIELD
のProgInfo
エントリを移動または追加。
src/cmd/dist/build.c
:cmd/gc
のビルド設定にpopt.c
を追加。cmd/5g
,cmd/6g
,cmd/8g
のビルド設定に../gc/popt.c
と../gc/popt.h
を追加。
コアとなるコードの解説
src/cmd/gc/popt.c
このファイルは、Goコンパイラのバックエンドで共通して使用される最適化ロジックをカプセル化しています。
noreturn
関数
int
noreturn(Prog *p)
{
Sym *s;
int i;
static Sym* symlist[10];
if(symlist[0] == S) {
symlist[0] = pkglookup("panicindex", runtimepkg);
symlist[1] = pkglookup("panicslice", runtimepkg);
symlist[2] = pkglookup("throwinit", runtimepkg);
symlist[3] = pkglookup("panic", runtimepkg);
symlist[4] = pkglookup("panicwrap", runtimepkg);
}
s = p->to.sym;
if(s == S)
return 0;
for(i=0; symlist[i]!=S; i++)
if(s == symlist[i])
return 1;
return 0;
}
この関数は、与えられたProg
(命令)が呼び出し命令であり、その呼び出し先がGoランタイムの特定の「戻らない」関数(例: panic
関連関数)であるかどうかをチェックします。symlist
に登録されたシンボル(関数名)と命令のターゲットシンボルを比較することで、戻らない関数呼び出しを識別します。コンパイラは、この情報を使って、その呼び出し以降のコードが到達不能であることを判断し、不要なコードを削除する最適化を行うことができます。
fixjmp
関数
fixjmp
関数は、ジャンプ命令の最適化を行う主要な関数です。これは複数のパスで構成されています。
-
パス1: ジャンプの解決とコードのマーク:
chasejmp
関数を使って、JMP
命令が別のJMP
命令を指している場合に、最終的なジャンプ先を解決します。これにより、JMP L1; L1: JMP L2
のような多重ジャンプをJMP L2
のように直接的なジャンプに変換します。- すべての命令を「dead」(到達不能)として初期マークします。
-
パス2: 到達可能なコードのマーク:
mark
関数を呼び出し、関数の開始点から到達可能なすべての命令を「alive」(到達可能)としてマークします。mark
関数は、命令のリンクをたどり、条件分岐や関数呼び出しのターゲットも再帰的にマークします。AJMP
(無条件ジャンプ)、ARET
(リターン)、AUNDEF
(未定義命令)に遭遇すると、そのパスのマークを停止します。
-
パス3: デッドコードの削除:
- 「dead」とマークされた命令(主に不要なジャンプ命令)を命令リストから削除します。
- ただし、関数の最後の
ARET
命令は、たとえ「dead」とマークされても、関数が必ずリターンするように残されます。
-
パス4: 次の命令へのジャンプの削除:
JMP
命令が、その直後の命令にジャンプしている場合(例:JMP L1; L1: MOV ...
)、そのJMP
命令を削除します。これは、ジャンプ命令が不要であり、単に次の命令に実行が移るためです。このパスは、パス1でジャンプチェインが完全に解決されている場合にのみ安全に実行できます。
src/cmd/{5g,6g,8g}/reg.c
これらのファイルからは、fixjmp
とnoreturn
関数の実装が削除されています。代わりに、gc/popt.h
をインクルードし、fixjmp
関数を呼び出すように変更されています。また、Prog
構造体のreg
またはregp
フィールドへのアクセスがopt
フィールドに置き換えられています。
src/cmd/{5g,6g,8g}/gg.h
および src/cmd/{5g,6g,8g}/opt.h
これらのヘッダファイルでは、Prog
構造体の定義が更新され、void* regp;
またはvoid* reg;
がvoid* opt;
に変更されています。これにより、最適化パスがProg
に関連付けるデータへのポインタがより汎用的な名前になります。また、opt.h
にはgc/popt.h
がインクルードされ、共通の最適化関数のプロトタイプが利用可能になります。
関連リンク
- Goコンパイラのソースコード: https://github.com/golang/go/tree/master/src/cmd/compile
- Goコンパイラの設計に関するドキュメント(古いものも含むが、概念理解に役立つ可能性あり): https://go.dev/doc/articles/go_compiler.html
参考にした情報源リンク
- Goのコミット履歴: https://github.com/golang/go/commits/master
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている
https://golang.org/cl/12789043
はGerritの変更リストへのリンクです) - Goコンパイラの内部構造に関する一般的な情報源(例: Goのブログ記事、カンファレンス発表など)
- コンパイラ最適化に関する一般的な知識(デッドコード削除、ジャンプ最適化など)