[インデックス 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のブログ記事、カンファレンス発表など)
- コンパイラ最適化に関する一般的な知識(デッドコード削除、ジャンプ最適化など)