[インデックス 10812] ファイルの概要
このコミットは、Goコンパイラ(gc
)のインライン化(inlining)機能に関する改善を含んでいます。具体的には、空のステートメント(OEMPTY
)の取り扱いと、ブランク識別子(_
)として宣言された引数のインライン化時の処理を修正しています。これにより、コンパイラの正確性と堅牢性が向上し、より多くのケースでインライン化が正しく適用されるようになります。
コミット
commit 7e6890a670e676f8040e690c7dc1409d0972bf22
Author: Luuk van Dijk <lvd@golang.org>
Date: Thu Dec 15 17:50:59 2011 +0100
gc: inlining, allow empty bodies, fix _ arguments.
R=rsc
CC=golang-dev
https://golang.org/cl/5487077
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7e6890a670e676f8040e690c7dc1409d0972bf22
元コミット内容
gc: inlining, allow empty bodies, fix _ arguments.
R=rsc
CC=golang-dev
https://golang.org/cl/5487077
変更の背景
Goコンパイラは、プログラムの実行性能を向上させるために、関数のインライン化を行います。インライン化とは、関数呼び出しのオーバーヘッドを削減するために、呼び出し元のコードに関数本体を直接埋め込む最適化手法です。しかし、コンパイラがコードを解析し、インライン化の可否を判断する際には、様々な構文要素や特殊な識別子の取り扱いを正確に行う必要があります。
このコミットが行われた背景には、以下の二つの具体的な問題があったと考えられます。
-
空のステートメント(
OEMPTY
)の不適切な処理: Go言語では、セミコロンのみで構成される空のステートメントが存在します。例えば、for {}
のような無限ループの本体が空である場合や、意図的に空のブロックを記述する場合などです。Goコンパイラのインライン化ロジックが、このような空のステートメントを適切に認識・処理できていなかった可能性があります。これにより、空のステートメントを含む関数が正しくインライン化されなかったり、コンパイルエラーや予期せぬ動作を引き起こしたりする可能性がありました。 -
ブランク識別子(
_
)引数の不適切な処理: Go言語のブランク識別子_
は、変数を宣言したがその値を使用しないことを明示的に示すために使用されます。関数の引数として_
が使われた場合、その引数は呼び出し側で値が渡されるものの、関数本体ではその値が使用されないことを意味します。インライン化のプロセスにおいて、コンパイラが_
で宣言された引数を通常の引数と同様に扱ってしまうと、不要なコードが生成されたり、最適化の機会が失われたりする可能性がありました。特に、インライン化された関数内で_
引数に対応する変数が生成され、それが未使用であると判断されずに、余計な処理が残ってしまうといった問題が考えられます。
これらの問題を解決し、Goコンパイラのインライン化機能をより堅牢で正確なものにすることが、このコミットの目的です。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラおよびGo言語の基本的な概念を理解しておく必要があります。
Goコンパイラ(gc
)の構造
Goコンパイラは、ソースコードを機械語に変換する役割を担います。その過程で、構文解析、型チェック、中間表現(IR)の生成、最適化、コード生成といった複数のフェーズを経ます。
src/cmd/gc
: これはGoコンパイラの主要なソースコードが置かれているディレクトリです。fmt.c
: コンパイラの内部表現(ASTノードなど)を人間が読める形式にフォーマットするためのコードが含まれています。デバッグ出力やエラーメッセージの生成などに使われます。inl.c
: 関数のインライン化に関するロジックが実装されているファイルです。どの関数をインライン化するかを決定し(caninl
)、実際にインライン化されたコードを生成する(mkinlcall
)処理が含まれます。
抽象構文木(AST)とノードの種類
Goコンパイラは、ソースコードを解析して抽象構文木(AST: Abstract Syntax Tree)を構築します。ASTは、プログラムの構造を木構造で表現したものです。ASTの各要素は「ノード」と呼ばれ、それぞれが特定の構文要素(変数宣言、関数呼び出し、ステートメントなど)を表します。
OEMPTY
: これはGoコンパイラの内部で使われるASTノードの種類の一つで、「空のステートメント」を表します。例えば、for {}
のような空のブロックや、単独のセミコロン;
などがこれに該当します。
関数のインライン化(Inlining)
インライン化は、コンパイラ最適化の一種です。
- 目的: 関数呼び出しのオーバーヘッド(スタックフレームのセットアップ、引数の渡し、戻り値の処理など)を削減し、プログラムの実行速度を向上させます。また、インライン化によって関数本体が呼び出し元に展開されることで、さらに別の最適化(定数伝播、デッドコード削除など)が可能になる場合があります。
- プロセス: コンパイラは、特定の条件(関数のサイズ、複雑さ、呼び出し回数など)を満たす関数をインライン化の候補と判断します。インライン化が決定されると、関数呼び出しサイトで、その関数のコードが直接埋め込まれます。
caninl
関数:inl.c
内にあるこの関数は、与えられた関数がインライン化可能かどうかを判断するロジックを含んでいます。mkinlcall
関数:inl.c
内にあるこの関数は、実際にインライン化された関数呼び出しのASTノードを生成する役割を担います。
ブランク識別子(Blank Identifier: _
)
Go言語のブランク識別子 _
は、特別な意味を持つ識別子です。
- 未使用の変数/引数:
_
は、変数を宣言したがその値を使用しないことを明示的に示すために使われます。例えば、x, _ := someFunc()
のように、someFunc
が複数の値を返す場合に、2番目の戻り値が不要であることを示します。関数の引数としてfunc foo(_, y int)
のように使われた場合、_
に対応する引数は呼び出し側から値が渡されますが、関数本体ではその値が使用されないことを意味します。 - インポート:
import _ "package"
のように、パッケージをインポートするが、そのパッケージの変数や関数を直接使用せず、パッケージの初期化(init
関数)のみを実行したい場合にも使われます。 - コンパイラの挙動: コンパイラは、
_
で宣言された変数が未使用であってもエラーを報告しません。これは、開発者が意図的にその値を無視していることをコンパイラに伝えるための仕組みです。
これらの概念を理解することで、コミットがGoコンパイラのどの部分に影響を与え、どのような問題を解決しようとしているのかが明確になります。
技術的詳細
このコミットは、Goコンパイラのフロントエンドと最適化フェーズにおける、ASTノードの処理とインライン化ロジックの改善に焦点を当てています。
src/cmd/gc/fmt.c
の変更
fmt.c
は、コンパイラの内部表現をデバッグやエラー報告のためにフォーマットする役割を担います。
stmtfmt
関数へのOEMPTY
の追加:stmtfmt
はステートメントノードをフォーマットする関数です。この変更により、OEMPTY
ノードが明示的に処理されるようになりました。case OEMPTY: break;
という記述は、OEMPTY
ノードが検出された場合に、特別なフォーマット処理は不要であり、単にスキップして次の処理に進むことを意味します。これは、空のステートメントがそれ自体で何かを表現する必要がないため、適切な挙動です。opprec
配列へのOEMPTY
の追加:opprec
は、演算子の優先順位を定義する配列です。[OEMPTY] = -1,
の追加は、OEMPTY
が演算子ではなくステートメントであるため、優先順位を持たないことを示します。-1
は通常、優先順位がない、または無関係であることを意味します。この変更は、コンパイラの内部的な整合性を保つためのものです。
これらの変更は、OEMPTY
ノードがコンパイラの様々な部分で正しく認識され、処理されるようにするための基盤を強化します。
src/cmd/gc/inl.c
の変更
inl.c
は、Goコンパイラのインライン化ロジックの核心部分です。
-
caninl
関数でのOEMPTY
の考慮:caninl
関数は、特定の関数がインライン化可能かどうかを判断します。以前のコードでは、OEMPTY
がコメントアウトされており、インライン化の判断基準から除外されていた可能性があります。case OEMPTY:
のコメント解除により、空のステートメントが関数本体に含まれていても、それがインライン化の妨げにならないように、caninl
が適切に評価するようになりました。これは、空のステートメントが関数の振る舞いに影響を与えないため、インライン化を妨げるべきではないという論理に基づいています。 -
mkinlcall
関数でのブランク識別子_
引数の修正:mkinlcall
関数は、実際にインライン化された関数呼び出しのASTを構築します。この関数内で、インライン化される関数の引数を処理するループがあります。- 変更前:
if(t->nname)
- 変更後:
if(t->nname && !isblank(t->nname))
この変更は非常に重要です。t->nname
は引数の名前を表すノードです。isblank(t->nname)
は、その引数名がブランク識別子_
であるかどうかをチェックする関数であると推測されます。 変更前は、引数に名前があれば(つまり_
でない限り)その引数をインライン化された呼び出しの変数リストに追加していました。しかし、Go言語のセマンティクスでは、_
で宣言された引数は使用されないため、インライン化されたコードでもその引数に対応する変数を生成したり、その値を処理したりする必要はありません。 この修正により、_
で宣言された引数はmkinlcall
の中で特別に扱われ、インライン化された呼び出しの変数リストに追加されなくなります。これにより、不要な変数やコードが生成されるのを防ぎ、より効率的なインライン化が可能になります。これは、Goのブランク識別子のセマンティクスをコンパイラの最適化フェーズに正しく反映させるための重要な修正です。
- 変更前:
test/cmp.go
の変更
このファイルは、Goコンパイラの比較テストに使用されるテストケースです。
func use(bool) {}
からvar global bool; func use(b bool) { global = b }
への変更: 元のuse
関数は引数bool
を受け取りますが、その引数を関数内で一切使用していませんでした。Goコンパイラは、未使用の引数や変数を最適化の一環として削除することがあります。特に、インライン化のテストを行う際に、引数が全く使用されない関数がインライン化されると、その引数に関連するコードが完全に削除されてしまい、テストの意図(例えば、引数のインライン化時の挙動を確認する)が達成できない可能性があります。 新しいuse
関数では、引数b
の値をグローバル変数global
に代入しています。これにより、引数b
が関数内で「使用される」ことになり、コンパイラがこの関数呼び出しや引数処理を完全に最適化して削除してしまうことを防ぎます。これは、インライン化のテストが意図通りに機能し、特にブランク識別子_
の引数処理の修正が正しく行われたことを検証するための、テストケースの堅牢性を高める変更です。
これらの変更は、Goコンパイラのインライン化ロジックが、Go言語のセマンティクス(特にブランク識別子)とASTノードの正しい処理をより厳密に遵守するように改善されたことを示しています。
コアとなるコードの変更箇所
src/cmd/gc/fmt.c
--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -914,6 +914,9 @@ stmtfmt(Fmt *f, Node *n)
fmtprint(f, "%#O", n->op);
break;
+ case OEMPTY:
+ break;
+
case OLABEL:
fmtprint(f, "%N: ", n->left);
break;
@@ -1018,6 +1021,7 @@ static int opprec[] = {
[ODCL] = -1,
[ODCLFIELD] = -1,
[ODEFER] = -1,
+ [OEMPTY] = -1,
[OFALL] = -1,
[OFOR] = -1,
[OIF] = -1,
src/cmd/gc/inl.c
--- a/src/cmd/gc/inl.c
+++ b/src/cmd/gc/inl.c
@@ -60,7 +60,7 @@ caninl(Node *fn)
case ORETURN:
case OAS:
case OAS2:
-// case OEMPTY: // TODO
+ case OEMPTY:
break;
@@ -449,7 +449,7 @@ mkinlcall(Node **np, Node *fn)
// TODO check that n->list->n is a call?
as->rlist = n->list;
for(t = getinargx(fn->type)->type; t; t=t->down) {
- if(t->nname) {
+ if(t->nname && !isblank(t->nname)) {
if(!t->nname->inlvar)
fatal("missing inlvar for %N\n", t->nname);
as->list = list(as->list, t->nname->inlvar);
@@ -460,7 +460,7 @@ mkinlcall(Node **np, Node *fn)
} else {
ll = n->list;
for(t = getinargx(fn->type)->type; t && ll; t=t->down) {
- if(t->nname) {
+ if(t->nname && !isblank(t->nname)) {
if(!t->nname->inlvar)
fatal("missing inlvar for %N\n", t->nname);
as->list = list(as->list, t->nname->inlvar);
test/cmp.go
--- a/test/cmp.go
+++ b/test/cmp.go
@@ -8,7 +8,8 @@ package main
import "unsafe"
-func use(bool) {}
+var global bool
+func use(b bool) { global = b }
func stringptr(s string) uintptr { return *(*uintptr)(unsafe.Pointer(&s)) }
コアとなるコードの解説
src/cmd/gc/fmt.c
の変更点
stmtfmt
関数: この関数は、Goコンパイラの内部でASTノードを文字列として表現する際に使用されます。case OEMPTY:
の追加は、空のステートメントノード(OEMPTY
)が検出された場合に、特別な出力を行わずに処理を継続することを示します。これは、空のステートメントが視覚的な表現を必要としないため、適切な処理です。opprec
配列: この配列は、Go言語の演算子の優先順位を定義します。[OEMPTY] = -1,
の追加は、OEMPTY
が演算子ではなくステートメントであるため、優先順位の概念が適用されないことを示します。-1
は、そのノードタイプが優先順位を持たないことを表す慣例的な値です。これらの変更は、コンパイラの内部的な整合性を保ち、OEMPTY
ノードがシステム全体で正しく扱われるようにするためのものです。
src/cmd/gc/inl.c
の変更点
caninl
関数: この関数は、特定の関数がインライン化の対象として適切かどうかを判断します。以前のバージョンでは、OEMPTY
のケースがコメントアウトされていました。このコメントを解除することで、OEMPTY
ノードが関数本体に含まれていても、それがインライン化の可否に影響を与えないように、caninl
が適切に評価するようになりました。これにより、空のステートメントを含む関数も正しくインライン化の候補として扱われるようになります。mkinlcall
関数: この関数は、実際にインライン化された関数呼び出しのASTを構築する際に、引数を処理する部分です。- 変更前:
if(t->nname)
- 変更後:
if(t->nname && !isblank(t->nname))
ここでt->nname
は引数の名前を表すASTノードです。isblank(t->nname)
は、その引数名がGo言語のブランク識別子_
であるかどうかをチェックする関数です。 この変更の核心は、ブランク識別子_
で宣言された引数を特別に扱う点にあります。Go言語では、_
で宣言された引数は関数内で使用されないことが保証されます。したがって、インライン化されたコードにおいても、これらの引数に対応する変数を生成したり、その値を処理したりする必要はありません。 変更後のコードは、「引数に名前があり(t->nname
が真)、かつその名前がブランク識別子_
でない場合(!isblank(t->nname)
が真)」にのみ、その引数をインライン化された呼び出しの変数リストに追加するように指示しています。これにより、_
引数に関連する不要なコードがインライン化された結果から排除され、生成されるコードの効率が向上します。
- 変更前:
test/cmp.go
の変更点
func use(bool) {}
からvar global bool; func use(b bool) { global = b }
: この変更は、テストの信頼性を高めるためのものです。元のuse
関数は引数を受け取りますが、その引数を関数内で使用していませんでした。Goコンパイラは、未使用の変数や引数を最適化の一環として削除することがあります。インライン化のテストにおいて、引数が全く使用されない関数がインライン化されると、引数に関連するコードが完全に削除されてしまい、テストの意図(例えば、引数のインライン化時の挙動を確認する)が達成できない可能性がありました。 新しいuse
関数では、引数b
の値をグローバル変数global
に代入しています。これにより、引数b
が関数内で「使用される」ことになり、コンパイラがこの関数呼び出しや引数処理を完全に最適化して削除してしまうことを防ぎます。この変更は、特にブランク識別子_
の引数処理の修正が正しく行われたことを検証するために、テストケースが確実に引数を「使用」している状態を作り出すことで、テストの堅牢性を保証します。
これらの変更は全体として、Goコンパイラのインライン化機能が、Go言語のセマンティクス(特にブランク識別子)とASTノードの正しい処理をより厳密に遵守するように改善されたことを示しています。これにより、コンパイラの正確性と生成されるコードの効率が向上します。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Goコンパイラのソースコード(GitHub): https://github.com/golang/go
- Goのブランク識別子に関する公式ブログ記事(英語): https://go.dev/blog/declaration-syntax (ブランク識別子について直接言及している記事ではないが、Goの宣言構文の背景を理解するのに役立つ)
参考にした情報源リンク
- Goコンパイラの内部構造に関する一般的な情報源(Goの公式ドキュメントやブログ、Goのソースコード自体)
- Go言語のブランク識別子に関する一般的な情報源
- コンパイラの最適化、特にインライン化に関する一般的な情報源
- GoのコードレビューシステムGerritのCLページ: https://golang.org/cl/5487077 (このコミットの元のレビューページ)
- GoのASTノードタイプに関する情報(Goコンパイラのソースコード内の定義など)# [インデックス 10812] ファイルの概要
このコミットは、Goコンパイラ(gc
)のインライン化(inlining)機能に関する改善を含んでいます。具体的には、空のステートメント(OEMPTY
)の取り扱いと、ブランク識別子(_
)として宣言された引数のインライン化時の処理を修正しています。これにより、コンパイラの正確性と堅牢性が向上し、より多くのケースでインライン化が正しく適用されるようになります。
コミット
commit 7e6890a670e676f8040e690c7dc1409d0972bf22
Author: Luuk van Dijk <lvd@golang.org>
Date: Thu Dec 15 17:50:59 2011 +0100
gc: inlining, allow empty bodies, fix _ arguments.
R=rsc
CC=golang-dev
https://golang.org/cl/5487077
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7e6890a670e676f8040e690c7dc1409d0972bf22
元コミット内容
gc: inlining, allow empty bodies, fix _ arguments.
R=rsc
CC=golang-dev
https://golang.org/cl/5487077
変更の背景
Goコンパイラは、プログラムの実行性能を向上させるために、関数のインライン化を行います。インライン化とは、関数呼び出しのオーバーヘッドを削減するために、呼び出し元のコードに関数本体を直接埋め込む最適化手法です。しかし、コンパイラがコードを解析し、インライン化の可否を判断する際には、様々な構文要素や特殊な識別子の取り扱いを正確に行う必要があります。
このコミットが行われた背景には、以下の二つの具体的な問題があったと考えられます。
-
空のステートメント(
OEMPTY
)の不適切な処理: Go言語では、セミコロンのみで構成される空のステートメントが存在します。例えば、for {}
のような無限ループの本体が空である場合や、意図的に空のブロックを記述する場合などです。Goコンパイラのインライン化ロジックが、このような空のステートメントを適切に認識・処理できていなかった可能性があります。これにより、空のステートメントを含む関数が正しくインライン化されなかったり、コンパイルエラーや予期せぬ動作を引き起こしたりする可能性がありました。 -
ブランク識別子(
_
)引数の不適切な処理: Go言語のブランク識別子_
は、変数を宣言したがその値を使用しないことを明示的に示すために使用されます。関数の引数として_
が使われた場合、その引数は呼び出し側で値が渡されるものの、関数本体ではその値が使用されないことを意味します。インライン化のプロセスにおいて、コンパイラが_
で宣言された引数を通常の引数と同様に扱ってしまうと、不要なコードが生成されたり、最適化の機会が失われたりする可能性がありました。特に、インライン化された関数内で_
引数に対応する変数が生成され、それが未使用であると判断されずに、余計な処理が残ってしまうといった問題が考えられます。
これらの問題を解決し、Goコンパイラのインライン化機能をより堅牢で正確なものにすることが、このコミットの目的です。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラおよびGo言語の基本的な概念を理解しておく必要があります。
Goコンパイラ(gc
)の構造
Goコンパイラは、ソースコードを機械語に変換する役割を担います。その過程で、構文解析、型チェック、中間表現(IR)の生成、最適化、コード生成といった複数のフェーズを経ます。
src/cmd/gc
: これはGoコンパイラの主要なソースコードが置かれているディレクトリです。fmt.c
: コンパイラの内部表現(ASTノードなど)を人間が読める形式にフォーマットするためのコードが含まれています。デバッグ出力やエラーメッセージの生成などに使われます。inl.c
: 関数のインライン化に関するロジックが実装されているファイルです。どの関数をインライン化するかを決定し(caninl
)、実際にインライン化されたコードを生成する(mkinlcall
)処理が含まれます。
抽象構文木(AST)とノードの種類
Goコンパイラは、ソースコードを解析して抽象構文木(AST: Abstract Syntax Tree)を構築します。ASTは、プログラムの構造を木構造で表現したものです。ASTの各要素は「ノード」と呼ばれ、それぞれが特定の構文要素(変数宣言、関数呼び出し、ステートメントなど)を表します。
OEMPTY
: これはGoコンパイラの内部で使われるASTノードの種類の一つで、「空のステートメント」を表します。例えば、for {}
のような空のブロックや、単独のセミコロン;
などがこれに該当します。
関数のインライン化(Inlining)
インライン化は、コンパイラ最適化の一種です。
- 目的: 関数呼び出しのオーバーヘッド(スタックフレームのセットアップ、引数の渡し、戻り値の処理など)を削減し、プログラムの実行速度を向上させます。また、インライン化によって関数本体が呼び出し元に展開されることで、さらに別の最適化(定数伝播、デッドコード削除など)が可能になる場合があります。
- プロセス: コンパイラは、特定の条件(関数のサイズ、複雑さ、呼び出し回数など)を満たす関数をインライン化の候補と判断します。インライン化が決定されると、関数呼び出しサイトで、その関数のコードが直接埋め込まれます。
caninl
関数:inl.c
内にあるこの関数は、与えられた関数がインライン化可能かどうかを判断するロジックを含んでいます。mkinlcall
関数:inl.c
内にあるこの関数は、実際にインライン化された関数呼び出しのASTノードを生成する役割を担います。
ブランク識別子(Blank Identifier: _
)
Go言語のブランク識別子 _
は、特別な意味を持つ識別子です。
- 未使用の変数/引数:
_
は、変数を宣言したがその値を使用しないことを明示的に示すために使われます。例えば、x, _ := someFunc()
のように、someFunc
が複数の値を返す場合に、2番目の戻り値が不要であることを示します。関数の引数としてfunc foo(_, y int)
のように使われた場合、その引数に対応する値は呼び出し側から渡されますが、関数本体ではその値が使用されないことを意味します。 - インポート:
import _ "package"
のように、パッケージをインポートするが、そのパッケージの変数や関数を直接使用せず、パッケージの初期化(init
関数)のみを実行したい場合にも使われます。 - コンパイラの挙動: コンパイラは、
_
で宣言された変数が未使用であってもエラーを報告しません。これは、開発者が意図的にその値を無視していることをコンパイラに伝えるための仕組みです。
これらの概念を理解することで、コミットがGoコンパイラのどの部分に影響を与え、どのような問題を解決しようとしているのかが明確になります。
技術的詳細
このコミットは、Goコンパイラのフロントエンドと最適化フェーズにおける、ASTノードの処理とインライン化ロジックの改善に焦点を当てています。
src/cmd/gc/fmt.c
の変更
fmt.c
は、コンパイラの内部表現をデバッグやエラー報告のためにフォーマットする役割を担います。
stmtfmt
関数へのOEMPTY
の追加:stmtfmt
はステートメントノードをフォーマットする関数です。この変更により、OEMPTY
ノードが明示的に処理されるようになりました。case OEMPTY: break;
という記述は、OEMPTY
ノードが検出された場合に、特別なフォーマット処理は不要であり、単にスキップして次の処理に進むことを意味します。これは、空のステートメントがそれ自体で何かを表現する必要がないため、適切な挙動です。opprec
配列へのOEMPTY
の追加:opprec
は、演算子の優先順位を定義する配列です。[OEMPTY] = -1,
の追加は、OEMPTY
が演算子ではなくステートメントであるため、優先順位を持たないことを示します。-1
は通常、優先順位がない、または無関係であることを意味します。この変更は、コンパイラの内部的な整合性を保つためのものです。
これらの変更は、OEMPTY
ノードがコンパイラの様々な部分で正しく認識され、処理されるようにするための基盤を強化します。
src/cmd/gc/inl.c
の変更
inl.c
は、Goコンパイラのインライン化ロジックの核心部分です。
-
caninl
関数でのOEMPTY
の考慮:caninl
関数は、特定の関数がインライン化可能かどうかを判断します。以前のコードでは、OEMPTY
がコメントアウトされており、インライン化の判断基準から除外されていた可能性があります。case OEMPTY:
のコメント解除により、空のステートメントが関数本体に含まれていても、それがインライン化の妨げにならないように、caninl
が適切に評価するようになりました。これは、空のステートメントが関数の振る舞いに影響を与えないため、インライン化を妨げるべきではないという論理に基づいています。 -
mkinlcall
関数でのブランク識別子_
引数の修正:mkinlcall
関数は、実際にインライン化された関数呼び出しのASTを構築します。この関数内で、インライン化される関数の引数を処理するループがあります。- 変更前:
if(t->nname)
- 変更後:
if(t->nname && !isblank(t->nname))
この変更は非常に重要です。t->nname
は引数の名前を表すノードです。isblank(t->nname)
は、その引数名がブランク識別子_
であるかどうかをチェックする関数であると推測されます。 変更前は、引数に名前があれば(つまり_
でない限り)その引数をインライン化された呼び出しの変数リストに追加していました。しかし、Go言語のセマンティクスでは、_
で宣言された引数は使用されないため、インライン化されたコードでもその引数に対応する変数を生成したり、その値を処理したりする必要はありません。 この修正により、_
で宣言された引数はmkinlcall
の中で特別に扱われ、インライン化された呼び出しの変数リストに追加されなくなります。これにより、不要な変数やコードが生成されるのを防ぎ、より効率的なインライン化が可能になります。これは、Goのブランク識別子のセマンティクスをコンパイラの最適化フェーズに正しく反映させるための重要な修正です。
- 変更前:
test/cmp.go
の変更
このファイルは、Goコンパイラの比較テストに使用されるテストケースです。
func use(bool) {}
からvar global bool; func use(b bool) { global = b }
への変更: 元のuse
関数は引数bool
を受け取りますが、その引数を関数内で一切使用していませんでした。Goコンパイラは、未使用の引数や変数を最適化の一環として削除することがあります。特に、インライン化のテストを行う際に、引数が全く使用されない関数がインライン化されると、その引数に関連するコードが完全に削除されてしまい、テストの意図(例えば、引数のインライン化時の挙動を確認する)が達成できない可能性があります。 新しいuse
関数では、引数b
の値をグローバル変数global
に代入しています。これにより、引数b
が関数内で「使用される」ことになり、コンパイラがこの関数呼び出しや引数処理を完全に最適化して削除してしまうことを防ぎます。これは、インライン化のテストが意図通りに機能し、特にブランク識別子_
の引数処理の修正が正しく行われたことを検証するための、テストケースの堅牢性を高める変更です。
これらの変更は全体として、Goコンパイラのインライン化ロジックが、Go言語のセマンティクス(特にブランク識別子)とASTノードの正しい処理をより厳密に遵守するように改善されたことを示しています。
コアとなるコードの変更箇所
src/cmd/gc/fmt.c
--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -914,6 +914,9 @@ stmtfmt(Fmt *f, Node *n)
fmtprint(f, "%#O", n->op);
break;
+ case OEMPTY:
+ break;
+
case OLABEL:
fmtprint(f, "%N: ", n->left);
break;
@@ -1018,6 +1021,7 @@ static int opprec[] = {
[ODCL] = -1,
[ODCLFIELD] = -1,
[ODEFER] = -1,
+ [OEMPTY] = -1,
[OFALL] = -1,
[OFOR] = -1,
[OIF] = -1,
src/cmd/gc/inl.c
--- a/src/cmd/gc/inl.c
+++ b/src/cmd/gc/inl.c
@@ -60,7 +60,7 @@ caninl(Node *fn)
case ORETURN:
case OAS:
case OAS2:
-// case OEMPTY: // TODO
+ case OEMPTY:
break;
@@ -449,7 +449,7 @@ mkinlcall(Node **np, Node *fn)
// TODO check that n->list->n is a call?
as->rlist = n->list;
for(t = getinargx(fn->type)->type; t; t=t->down) {
- if(t->nname) {
+ if(t->nname && !isblank(t->nname)) {
if(!t->nname->inlvar)
fatal("missing inlvar for %N\\n", t->nname);
as->list = list(as->list, t->nname->inlvar);
@@ -460,7 +460,7 @@ mkinlcall(Node **np, Node *fn)
} else {
ll = n->list;
for(t = getinargx(fn->type)->type; t && ll; t=t->down) {
- if(t->nname) {
+ if(t->nname && !isblank(t->nname)) {
if(!t->nname->inlvar)
fatal("missing inlvar for %N\\n", t->nname);
as->list = list(as->list, t->nname->inlvar);
test/cmp.go
--- a/test/cmp.go
+++ b/test/cmp.go
@@ -8,7 +8,8 @@ package main
import "unsafe"
-func use(bool) {}
+var global bool
+func use(b bool) { global = b }
func stringptr(s string) uintptr { return *(*uintptr)(unsafe.Pointer(&s)) }
コアとなるコードの解説
src/cmd/gc/fmt.c
の変更点
stmtfmt
関数: この関数は、Goコンパイラの内部でASTノードを文字列として表現する際に使用されます。case OEMPTY:
の追加は、空のステートメントノード(OEMPTY
)が検出された場合に、特別な出力を行わずに処理を継続することを示します。これは、空のステートメントが視覚的な表現を必要としないため、適切な処理です。opprec
配列: この配列は、Go言語の演算子の優先順位を定義します。[OEMPTY] = -1,
の追加は、OEMPTY
が演算子ではなくステートメントであるため、優先順位の概念が適用されないことを示します。-1
は、そのノードタイプが優先順位を持たないことを表す慣例的な値です。これらの変更は、コンパイラの内部的な整合性を保ち、OEMPTY
ノードがシステム全体で正しく扱われるようにするためのものです。
src/cmd/gc/inl.c
の変更点
caninl
関数: この関数は、特定の関数がインライン化の対象として適切かどうかを判断します。以前のバージョンでは、OEMPTY
のケースがコメントアウトされていました。このコメントを解除することで、OEMPTY
ノードが関数本体に含まれていても、それがインライン化の可否に影響を与えないように、caninl
が適切に評価するようになりました。これにより、空のステートメントを含む関数も正しくインライン化の候補として扱われるようになります。mkinlcall
関数: この関数は、実際にインライン化された関数呼び出しのASTを構築する際に、引数を処理する部分です。- 変更前:
if(t->nname)
- 変更後:
if(t->nname && !isblank(t->nname))
ここでt->nname
は引数の名前を表すASTノードです。isblank(t->nname)
は、その引数名がGo言語のブランク識別子_
であるかどうかをチェックする関数です。 この変更の核心は、ブランク識別子_
で宣言された引数を特別に扱う点にあります。Go言語では、_
で宣言された引数は関数内で使用されないことが保証されます。したがって、インライン化されたコードにおいても、これらの引数に対応する変数を生成したり、その値を処理したりする必要はありません。 変更後のコードは、「引数に名前があり(t->nname
が真)、かつその名前がブランク識別子_
でない場合(!isblank(t->nname)
が真)」にのみ、その引数をインライン化された呼び出しの変数リストに追加するように指示しています。これにより、_
引数に関連する不要なコードがインライン化された結果から排除され、生成されるコードの効率が向上します。
- 変更前:
test/cmp.go
の変更点
func use(bool) {}
からvar global bool; func use(b bool) { global = b }
: この変更は、テストの信頼性を高めるためのものです。元のuse
関数は引数を受け取りますが、その引数を関数内で使用していませんでした。Goコンパイラは、未使用の変数や引数を最適化の一環として削除することがあります。インライン化のテストにおいて、引数が全く使用されない関数がインライン化されると、引数に関連するコードが完全に削除されてしまい、テストの意図(例えば、引数のインライン化時の挙動を確認する)が達成できない可能性がありました。 新しいuse
関数では、引数b
の値をグローバル変数global
に代入しています。これにより、引数b
が関数内で「使用される」ことになり、コンパイラがこの関数呼び出しや引数処理を完全に最適化して削除してしまうことを防ぎます。この変更は、特にブランク識別子_
の引数処理の修正が正しく行われたことを検証するために、テストケースが確実に引数を「使用」している状態を作り出すことで、テストの堅牢性を保証します。
これらの変更は全体として、Goコンパイラのインライン化機能が、Go言語のセマンティクス(特にブランク識別子)とASTノードの正しい処理をより厳密に遵守するように改善されたことを示しています。これにより、コンパイラの正確性と生成されるコードの効率が向上します。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Goコンパイラのソースコード(GitHub): https://github.com/golang/go
- Goのブランク識別子に関する公式ブログ記事(英語): https://go.dev/blog/declaration-syntax (ブランク識別子について直接言及している記事ではないが、Goの宣言構文の背景を理解するのに役立つ)
参考にした情報源リンク
- Goコンパイラの内部構造に関する一般的な情報源(Goの公式ドキュメントやブログ、Goのソースコード自体)
- Go言語のブランク識別子に関する一般的な情報源
- コンパイラの最適化、特にインライン化に関する一般的な情報源
- GoのコードレビューシステムGerritのCLページ: https://golang.org/cl/5487077 (このコミットの元のレビューページ)
- GoのASTノードタイプに関する情報(Goコンパイラのソースコード内の定義など)