[インデックス 15109] ファイルの概要
このコミットは、Goコンパイラのインライン化処理において、インライン化された関数内のラベルが重複する問題を修正するものです。具体的には、goto
文やラベルが使用されている関数が複数回インライン化される際に、生成されるラベル名が衝突しないように、一意な識別子を付加する変更が加えられました。これにより、issue #4748
で報告されたコンパイルエラーが解決されます。
コミット
commit 09a17ca1f113b7959391b0daf49ecfcd930cf30b
Author: Russ Cox <rsc@golang.org>
Date: Sun Feb 3 11:19:22 2013 -0500
cmd/gc: make inlined labels distinct
Fixes #4748.
R=ken2
CC=golang-dev
https://golang.org/cl/7261044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/09a17ca1f113b7959391b0daf49ecfcd930cf30b
元コミット内容
cmd/gc: make inlined labels distinct
Fixes #4748.
R=ken2
CC=golang-dev
https://golang.org/cl/7261044
変更の背景
この変更は、Goコンパイラにおけるバグ issue #4748
を修正するために行われました。このバグは、goto
文とラベルを含む関数が複数回インライン化される場合に発生しました。
Goコンパイラは、パフォーマンス向上のために、小さな関数呼び出しを呼び出し元に直接展開する「インライン化」という最適化を行います。しかし、インライン化される関数内にgoto
文と対応するラベル(例: exit:
)が存在し、その関数がコード内で複数回呼び出され、結果として複数回インライン化されると問題が発生しました。
具体的には、コンパイラがインライン化されたコードを生成する際、同じラベル名(例: exit
)が複数回定義されてしまい、コンパイラが「重複したラベル定義」としてエラーを報告していました。これは、インライン化によって関数の本体がコピーされる際に、その内部のラベル名が一意に保たれないために起こる問題でした。
このコミットは、このラベル名の衝突を回避し、インライン化が正しく行われるようにするために導入されました。
前提知識の解説
Goコンパイラ (cmd/gc
)
cmd/gc
は、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。コンパイルプロセスには、字句解析、構文解析、意味解析、中間コード生成、最適化、コード生成などが含まれます。
インライン化 (Inlining)
インライン化は、コンパイラ最適化の一種です。関数呼び出しのオーバーヘッド(スタックフレームの作成、引数の渡し、戻り値の処理など)を削減するために、呼び出される関数の本体を呼び出し元のコードに直接埋め込む(インライン展開する)技術です。これにより、実行時のパフォーマンスが向上する可能性があります。Goコンパイラは、特定の条件(関数が小さい、呼び出し回数が多いなど)を満たす関数に対して自動的にインライン化を適用します。
ラベルとgoto
文
Go言語には、他の多くのプログラミング言語と同様に、goto
文とラベルが存在します。goto
文は、プログラムの実行フローを、指定されたラベルの位置に直接ジャンプさせるために使用されます。ラベルは、labelName:
のように定義され、goto labelName
で参照されます。
func example() {
// ...
goto end
// ...
end:
// ...
}
goto
文は、コードの可読性を損なう可能性があるため、Goでは通常、for
ループのbreak
やcontinue
、switch
文など、より構造化された制御フローを使用することが推奨されます。しかし、特定の状況(例えば、エラーハンドリングやリソースのクリーンアップなど)でgoto
が有効な場合もあります。
抽象構文木 (AST) とノード
コンパイラは、ソースコードを直接操作するのではなく、それを抽象構文木(AST)というツリー構造に変換して処理します。ASTの各要素は「ノード」と呼ばれ、変数、関数呼び出し、演算子、制御構造(if
、for
、goto
など)などを表現します。コンパイラの最適化やコード生成は、このASTを走査・変換することで行われます。
技術的詳細
このコミットの核心は、インライン化されたコード内でラベル名が衝突する問題を解決することです。Goコンパイラのインライン化処理は、src/cmd/gc/inl.c
ファイルで実装されています。
問題は、inlsubst
関数がASTノードを走査し、インライン化のためにノードを置換する際に発生しました。特に、OGOTO
(goto
文)とOLABEL
(ラベル定義)のノードが処理される際に、元のラベル名がそのままコピーされてしまうため、同じ関数が複数回インライン化されると、同じ名前のラベルが複数生成されてしまうのです。
この修正では、この問題を解決するために以下のメカニズムが導入されました。
-
インライン化世代カウンター (
inlgen
) の導入:static int inlgen;
という静的変数がsrc/cmd/gc/inl.c
に追加されました。このカウンターは、関数がインライン化されるたびにインクリメントされます。これにより、各インライン化操作に一意の「世代」番号が割り当てられます。 -
ラベル名の動的生成:
inlsubst
関数内で、OGOTO
およびOLABEL
ノードが処理される際に、新しいラベル名が動的に生成されるようになりました。smprint("%s·%d", n->left->sym->name, inlgen)
という形式の文字列フォーマットが使用されます。n->left->sym->name
: 元のラベル名(例:exit
)inlgen
: 現在のインライン化世代カウンターの値
これにより、例えば
exit
というラベルは、最初のインライン化ではexit·1
、2回目のインライン化ではexit·2
のように、一意な名前に変更されます。·
(中点) は、Goの内部リンカが使用する特殊な区切り文字であり、ユーザーが定義する識別子と衝突しないように設計されています。 -
新しいシンボルの作成: 生成された新しいラベル名(例:
exit·1
)は、lookup(p)
を通じて新しいシンボルとして登録され、newname
で新しい名前ノードが作成されます。これにより、コンパイラは各インライン化されたラベルを別々のエンティティとして認識し、名前の衝突を回避できます。
この変更により、goto
文とラベルを含む関数が何度インライン化されても、それぞれのインスタンスでラベル名が一意になり、コンパイルエラーが発生しなくなりました。
コアとなるコードの変更箇所
src/cmd/gc/inl.c
--- a/src/cmd/gc/inl.c
+++ b/src/cmd/gc/inl.c
@@ -510,6 +510,8 @@ tinlvar(Type *t)
return nblank;
}
+static int inlgen;
+
// if *np is a call, and fn is a function with an inlinable body, substitute *np with an OINLCALL.
// On return ninit has the parameter assignments, the nbody is the
// inlined function body and list, rlist contain the input, output
@@ -730,6 +732,7 @@ mkinlcall1(Node **np, Node *fn, int isddd)
}
inlretlabel = newlabel();
+ inlgen++;
body = inlsubstlist(fn->inl);
body = list(body, nod(OGOTO, inlretlabel, N)); // avoid 'not used' when function doesnt have return
@@ -855,6 +858,7 @@ inlsubstlist(NodeList *ll)
static Node*
inlsubst(Node *n)
{
+ char *p;
Node *m, *as;
NodeList *ll;
@@ -897,6 +901,16 @@ inlsubst(Node *n)
typecheck(&m, Etop);
// dump("Return after substitution", m);
return m;
+
+ case OGOTO:
+ case OLABEL:
+ m = nod(OXXX, N, N);
+ *m = *n;
+ m->ninit = nil;
+ p = smprint("%s·%d", n->left->sym->name, inlgen);
+ m->left = newname(lookup(p));
+ free(p);
+ return m;
}
test/fixedbugs/issue4748.go
--- /dev/null
+++ b/test/fixedbugs/issue4748.go
@@ -0,0 +1,20 @@
+// run
+
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Issue 4748.
+// This program used to complain because inlining created two exit labels.
+
+package main
+
+func jump() {
+ goto exit
+exit:
+ return
+}
+func main() {
+ jump()
+ jump()
+}
コアとなるコードの解説
src/cmd/gc/inl.c
の変更点
-
static int inlgen;
の追加: これは、インライン化の「世代」を追跡するための静的カウンターです。コンパイラ全体で共有され、インライン化が実行されるたびにその値が更新されます。 -
inlgen++;
の追加 (mkinlcall1
関数内):mkinlcall1
関数は、実際にインライン化を行う主要な関数の一つです。この関数が呼び出され、関数がインライン化される直前にinlgen
がインクリメントされます。これにより、各インライン化操作が異なるinlgen
の値を持つことになり、生成されるラベル名の一意性が保証されます。 -
inlsubst
関数内のcase OGOTO:
とcase OLABEL:
の追加:inlsubst
関数は、インライン化される関数のASTを走査し、必要に応じてノードを置換する役割を担います。OGOTO
とOLABEL
のケースが追加されました。これは、goto
文とラベル定義のノードを特別に処理することを示します。m = nod(OXXX, N, N); *m = *n; m->ninit = nil;
: これは、元のノードn
のコピーm
を作成し、初期化リストをクリアする標準的なパターンです。p = smprint("%s·%d", n->left->sym->name, inlgen);
: ここが最も重要な変更点です。smprint
は、Goコンパイラ内部で使用される文字列フォーマット関数です。元のラベル名 (n->left->sym->name
) と現在のinlgen
の値を組み合わせて、新しい一意なラベル名(例:exit·1
)を生成します。·
(中点) は、Goの内部リンカが使用する特殊な文字で、ユーザーが定義する識別子と衝突しないように設計されています。m->left = newname(lookup(p));
: 生成された新しいラベル名p
を使用して、lookup
でシンボルテーブルから対応するシンボルを検索(または新規作成)し、newname
で新しい名前ノードを作成してm->left
に割り当てます。これにより、AST内のラベル参照が新しい一意な名前に更新されます。free(p);
:smprint
で割り当てられたメモリを解放します。
test/fixedbugs/issue4748.go
の変更点
このファイルは、issue #4748
で報告されたバグを再現し、このコミットによって修正されたことを検証するための新しいテストケースです。
func jump()
: この関数は、goto exit
とexit:
ラベルを含んでいます。この関数がインライン化の対象となります。func main()
:main
関数内でjump()
が2回呼び出されています。この2回の呼び出しがインライン化されると、修正前はexit:
ラベルが重複して定義されることになり、コンパイルエラーが発生していました。
このテストケースは、// run
ディレクティブによって、Goのテストスイートがこのファイルをコンパイルして実行することを指示しています。このコミットが適用された後、このテストはエラーなくコンパイル・実行されるようになり、バグが修正されたことを確認できます。
関連リンク
- Go Issue 4748: https://github.com/golang/go/issues/4748
- Go CL 7261044: https://golang.org/cl/7261044
参考にした情報源リンク
- Go Issue 4748 の詳細
- Goコンパイラのインライン化に関する一般的な情報
- Go言語の
goto
文とラベルに関するドキュメント - Goコンパイラのソースコード (
src/cmd/gc/inl.c
) の分析