Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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.gotwo.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構造体の内容を整形して出力します。

fmtmodeFExp

fmtmodeは、fmt.c内で使用されるグローバルまたはローカルなフラグであり、型情報のフォーマット方法や詳細度を制御するために用いられていたと考えられます。FExpはそのfmtmodeが取りうる値の一つであり、特定のコンテキスト(例えば、エクスポートされたシンボルの型をフォーマットする場合や、式(Expression)の型をフォーマットする場合など)を示していた可能性があります。このコミットでは、fmtmodeFExpである場合にのみ、特定の型情報の出力ロジックを条件分岐させることで、バグを回避しています。

技術的詳細

このバグは、Goコンパイラのgcが型情報を整形する際に、インライン化された関数の戻り値の型を誤って処理することに起因していました。src/cmd/gc/fmt.c内のtypefmt関数は、Type構造体を受け取り、その内容を整形して出力します。case 1のブロックは、特定の種類の型(おそらく構造体のフィールドや関数の戻り値など)の内部型情報を再帰的に出力するロジックを含んでいました。

問題は、このcase 1のロジックが、fmtmodeFExpである特定の状況下で、インライン化された関数の型情報と衝突し、誤った出力や内部的な不整合を引き起こしていた点にあります。getoutargx(t)->type->typeという表現は、tという型から、その出力引数(戻り値)の型、さらにその内部の型へとアクセスしていることを示しており、複雑な型構造(例えば、多層的なポインタや構造体、インターフェースなど)を扱う際に問題が発生しやすかったと考えられます。

コミットによる修正は、fmtmodeFExpでない場合にのみ、この特定の型出力ロジックを実行するように条件を追加することで、この問題を解決しています。これにより、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でないならば、この特定の型出力ロジックを実行する」という意味になります。つまり、fmtmodeFExpである場合には、このfmtprintの行はスキップされます。

この変更の意図は、FExpモードで型をフォーマットする際に、getoutargx(t)->type->typeが返す型情報が、インライン化されたコードのコンテキストで問題を引き起こしていたため、その出力を抑制することにあります。これにより、コンパイラがインライン化されたコードの型を処理する際の内部的な矛盾や誤った型推論が解消され、コンパイルエラーや不正なコード生成を防ぐことができます。

追加されたテストケースは、名前付き戻り値を持つ関数F3を定義し、それを呼び出すことで、このインライン化バグが再現されることを確認するために使用されました。F3のような特定の関数構造が、FExpモードでの型フォーマットとインライン化の相互作用において問題を引き起こすトリガーとなっていたと考えられます。

関連リンク

参考にした情報源リンク

  • Go言語の歴史とコンパイラの変遷に関する情報 (Go 1.5でのコンパイラ書き換えなど):
  • Go言語のコンパイラ内部に関する一般的な情報(C言語時代の詳細なドキュメントは少ないですが、概念的な理解に役立つもの)