[インデックス 11119] ファイルの概要
コミット
- コミットハッシュ:
524fb81c41ea559306a5ee3dbaf60fa6cda2479f
- 作者: Russ Cox rsc@golang.org
- 日付: 2012年1月11日 水曜日 20:32:02 -0500
- コミットメッセージ:
gc: inlining bug
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/524fb81c41ea559306a5ee3dbaf60fa6cda2479f
元コミット内容
commit 524fb81c41ea559306a5ee3dbaf60fa6cda2479f
Author: Russ Cox <rsc@golang.org>
Date: Wed Jan 11 20:32:02 2012 -0500
gc: inlining bug
R=lvd
CC=golang-dev
https://golang.org/cl/5533078
---
src/cmd/gc/fmt.c | 6 ++++--
test/fixedbugs/bug392.dir/one.go | 3 +++
test/fixedbugs/bug392.dir/two.go | 2 +-
3 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/cmd/gc/fmt.c b/src/cmd/gc/fmt.c
index f49c703f36..4afd6c42bf 100644
--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -669,8 +669,10 @@ typefmt(Fmt *fp, Type *t)\n case 0:\n \tbreak;\n case 1:\n-\t\t\tfmtprint(fp, " %T", getoutargx(t)->type->type);\t // struct->field->field's type
-\t\t\tbreak;
+\t\t\tif(fmtmode != FExp) {\n+\t\t\t\tfmtprint(fp, " %T", getoutargx(t)->type->type);\t // struct->field->field's type
+\t\t\t\tbreak;
+\t\t\t}
\t\tdefault:\n \t\t\tfmtprint(fp, " %T", getoutargx(t));\n \t\t\tbreak;
diff --git a/test/fixedbugs/bug392.dir/one.go b/test/fixedbugs/bug392.dir/one.go
index f086ebe4e2..a7017255e5 100644
--- a/test/fixedbugs/bug392.dir/one.go
+++ b/test/fixedbugs/bug392.dir/one.go
@@ -14,6 +14,9 @@ func F1(T *T) bool { return T == nil }\n // Issue 2682.\n func F2(c chan int) bool { return c == (<-chan int)(nil) }\n \n+// Use of single named return value.\n+func F3() (ret []int) { return append(ret, 1) }\n+\n // Call of inlined method with blank receiver.\n func (_ *T) M() int { return 1 }\n func (t *T) MM() int { return t.M() }\
diff --git a/test/fixedbugs/bug392.dir/two.go b/test/fixedbugs/bug392.dir/two.go
index 3704e65c5e..b0ce26d39a 100644
--- a/test/fixedbugs/bug392.dir/two.go
+++ b/test/fixedbugs/bug392.dir/two.go
@@ -12,9 +12,9 @@ import "./one"\n func use() {\n one.F1(nil)\n one.F2(nil)\n+\tone.F3()\n \n var t *one.T\n t.M()\n t.MM()\n }\n-
変更の背景
このコミットは、Goコンパイラ(gc
)におけるインライン化に関連するバグを修正するものです。具体的には、コンパイラの型フォーマット処理において、特定の条件下で誤った型情報が出力される問題に対処しています。このバグは、関数がインライン化される際に、その関数の戻り値の型が正しく処理されない場合に発生していたと考えられます。
当時のGoコンパイラ(Go 1.5以前)はC言語で記述されており、src/cmd/gc/fmt.c
はその型情報を整形・出力する役割を担っていました。このファイルへの変更は、コンパイラが内部的に型を表現する際に使用するfmtmode
というフラグがFExp
(おそらく「Format Exported」または「Format Expression」の略)である場合に、特定の型情報の出力が抑制されるようにすることで、インライン化されたコードの型推論や検証プロセスにおける不整合を解消することを目的としています。
テストケースの追加(test/fixedbugs/bug392.dir/one.go
とtwo.go
)から、このバグが特に名前付き戻り値を持つ関数のインライン化に関連していた可能性が示唆されます。
前提知識の解説
Goコンパイラ (gc
)
Go言語の公式コンパイラはgc
と呼ばれます。このコミットが作成された2012年当時、gc
は主にC言語で実装されていました。Go 1.5(2015年リリース)で、コンパイラ自体がGo言語で書き直されましたが、このコミットはそれ以前のC言語版コンパイラにおける挙動を修正するものです。gc
は、Goのソースコードを機械語に変換するだけでなく、型チェック、最適化(インライン化を含む)、コード生成などの様々なコンパイルフェーズを担当します。
インライン化 (Inlining)
インライン化は、コンパイラ最適化の一種です。関数呼び出しのオーバーヘッドを削減するために、呼び出される関数の本体を呼び出し元のコードに直接埋め込む処理を指します。これにより、関数呼び出しのスタックフレームの作成や破棄、引数の受け渡しといったコストが不要になり、プログラムの実行速度が向上する可能性があります。しかし、インライン化はコードサイズの増加を招くこともあり、コンパイラはヒューリスティックに基づいてインライン化を行うかどうかを決定します。インライン化の過程で、コンパイラは関数の型情報や引数、戻り値の型などを正確に追跡する必要があります。
src/cmd/gc/fmt.c
と型フォーマット
src/cmd/gc/fmt.c
は、当時のGoコンパイラ(gc
)のバックエンドの一部であり、コンパイラが内部的に扱う型(Type
構造体など)を整形して出力するためのユーティリティ関数群を提供していました。これはデバッグ情報、エラーメッセージ、あるいはコンパイラの異なるフェーズ間での情報伝達のために使用されます。fmtprint
関数は、C言語のprintf
に似た機能を提供し、%T
のようなフォーマット指定子を使ってType
構造体の内容を整形して出力します。
fmtmode
とFExp
fmtmode
は、fmt.c
内で使用されるグローバルまたはローカルなフラグであり、型情報のフォーマット方法や詳細度を制御するために用いられていたと考えられます。FExp
はそのfmtmode
が取りうる値の一つであり、特定のコンテキスト(例えば、エクスポートされたシンボルの型をフォーマットする場合や、式(Expression)の型をフォーマットする場合など)を示していた可能性があります。このコミットでは、fmtmode
がFExp
である場合にのみ、特定の型情報の出力ロジックを条件分岐させることで、バグを回避しています。
技術的詳細
このバグは、Goコンパイラのgc
が型情報を整形する際に、インライン化された関数の戻り値の型を誤って処理することに起因していました。src/cmd/gc/fmt.c
内のtypefmt
関数は、Type
構造体を受け取り、その内容を整形して出力します。case 1
のブロックは、特定の種類の型(おそらく構造体のフィールドや関数の戻り値など)の内部型情報を再帰的に出力するロジックを含んでいました。
問題は、このcase 1
のロジックが、fmtmode
がFExp
である特定の状況下で、インライン化された関数の型情報と衝突し、誤った出力や内部的な不整合を引き起こしていた点にあります。getoutargx(t)->type->type
という表現は、t
という型から、その出力引数(戻り値)の型、さらにその内部の型へとアクセスしていることを示しており、複雑な型構造(例えば、多層的なポインタや構造体、インターフェースなど)を扱う際に問題が発生しやすかったと考えられます。
コミットによる修正は、fmtmode
がFExp
でない場合にのみ、この特定の型出力ロジックを実行するように条件を追加することで、この問題を解決しています。これにより、FExp
モードでの型フォーマット時に、インライン化によって引き起こされる可能性のある誤った型情報の出力が抑制され、コンパイラの安定性と正確性が向上しました。
コアとなるコードの変更箇所
src/cmd/gc/fmt.c
ファイルのtypefmt
関数内、case 1
の箇所に以下の変更が加えられました。
--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -669,8 +669,10 @@ typefmt(Fmt *fp, Type *t)\n case 0:\n \tbreak;\n case 1:\n-\t\t\tfmtprint(fp, " %T", getoutargx(t)->type->type);\t // struct->field->field's type
-\t\t\tbreak;
+\t\t\tif(fmtmode != FExp) {\n+\t\t\t\tfmtprint(fp, " %T", getoutargx(t)->type->type);\t // struct->field->field's type
+\t\t\t\tbreak;
+\t\t\t}
\t\tdefault:\n \t\t\tfmtprint(fp, " %T", getoutargx(t));\n \t\t\tbreak;
また、このバグを再現し、修正を検証するためのテストケースが追加されています。
test/fixedbugs/bug392.dir/one.go
:
// Use of single named return value.
func F3() (ret []int) { return append(ret, 1) }
test/fixedbugs/bug392.dir/two.go
:
import "./one"
func use() {
one.F1(nil)
one.F2(nil)
one.F3() // 新しく追加されたF3関数の呼び出し
var t *one.T
t.M()
t.MM()
}
コアとなるコードの解説
src/cmd/gc/fmt.c
の変更は、typefmt
関数内のcase 1
ブロックに条件分岐を追加しています。
元のコードでは、case 1
の場合に無条件でfmtprint(fp, " %T", getoutargx(t)->type->type);
が実行され、型情報が出力されていました。この行は、コメントにもあるように「struct->field->field's type」のような、ネストされた型構造の内部型を出力するためのものです。
修正後のコードでは、この出力処理の前にif(fmtmode != FExp)
という条件が追加されています。
fmtmode
: コンパイラの型フォーマットモードを示す内部フラグ。FExp
:fmtmode
が取りうる値の一つで、特定のフォーマットコンテキスト(例えば、エクスポートされたシンボルや式の型を扱う場合)を示します。
この条件は、「もし現在の型フォーマットモードがFExp
でないならば、この特定の型出力ロジックを実行する」という意味になります。つまり、fmtmode
がFExp
である場合には、このfmtprint
の行はスキップされます。
この変更の意図は、FExp
モードで型をフォーマットする際に、getoutargx(t)->type->type
が返す型情報が、インライン化されたコードのコンテキストで問題を引き起こしていたため、その出力を抑制することにあります。これにより、コンパイラがインライン化されたコードの型を処理する際の内部的な矛盾や誤った型推論が解消され、コンパイルエラーや不正なコード生成を防ぐことができます。
追加されたテストケースは、名前付き戻り値を持つ関数F3
を定義し、それを呼び出すことで、このインライン化バグが再現されることを確認するために使用されました。F3
のような特定の関数構造が、FExp
モードでの型フォーマットとインライン化の相互作用において問題を引き起こすトリガーとなっていたと考えられます。
関連リンク
- Go言語の公式GitHubリポジトリ: https://github.com/golang/go
- このコミットのGitHubページ: https://github.com/golang/go/commit/524fb81c41ea559306a5ee3dbaf60fa6cda2479f
- Go言語のインライン化に関する一般的な情報 (Go 1.5以降のGo言語で書かれたコンパイラに関する情報が多いですが、概念は共通です):
- Goのインライン化の仕組み (Go公式ブログ): https://go.dev/blog/inlining (これはGo 1.5以降のコンパイラに関するものですが、インライン化の概念理解に役立ちます)
参考にした情報源リンク
- Go言語の歴史とコンパイラの変遷に関する情報 (Go 1.5でのコンパイラ書き換えなど):
- The Go Programming Language (Wikipedia): https://en.wikipedia.org/wiki/Go_(programming_language)
- Go 1.5 Release Notes: https://go.dev/doc/go1.5 (特に"Compiler and Runtime"セクション)
- Go言語のコンパイラ内部に関する一般的な情報(C言語時代の詳細なドキュメントは少ないですが、概念的な理解に役立つもの)
- Go Compiler Internals (古い情報を含む可能性あり): https://go.dev/doc/compiler