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

[インデックス 16175] ファイルの概要

このコミットは、Goコンパイラ(cmd/gc)におけるメソッド値のインライン化を無効にする変更を含んでいます。具体的には、以下のファイルが変更されました。

  • src/cmd/gc/inl.c: コンパイラのインライン化ロジックを定義するC言語のソースファイル。
  • test/fixedbugs/issue5259.dir/bug.go: 問題を再現するためのGoのコード。
  • test/fixedbugs/issue5259.dir/main.go: bug.go を利用して問題を再現するメイン関数。
  • test/fixedbugs/issue5259.go: テストスイートからこのバグテストを実行するための設定ファイル。

コミット

commit 7b8e08617ea0d2b119766e0fd893fbf4502280e8
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date:   Sat Apr 13 08:22:16 2013 +0200

    cmd/gc: disable inlining of method values
    
    They caused internal compiler errors and they're expensive enough that inlining them doesn't make sense.
    
    Fixes #5259.
    
    R=golang-dev, r, iant, remyoudompheng
    CC=golang-dev
    https://golang.org/cl/8636043
---
 src/cmd/gc/inl.c                     |  1 +
 test/fixedbugs/issue5259.dir/bug.go  | 17 +++++++++++++++++
 test/fixedbugs/issue5259.dir/main.go | 16 ++++++++++++++++
 test/fixedbugs/issue5259.go          |  9 +++++++++
 4 files changed, 43 insertions(+)

diff --git a/src/cmd/gc/inl.c b/src/cmd/gc/inl.c
index 850bb36ec7..f77b51d707 100644
--- a/src/cmd/gc/inl.c
+++ b/src/cmd/gc/inl.c
@@ -188,6 +188,7 @@ ishairy(Node *n, int *budget)
 		break;\n
 	case OCLOSURE:\n
+\tcase OCALLPART:\n
 	case ORANGE:\n
 	case OFOR:\n
 	case OSELECT:\ndiff --git a/test/fixedbugs/issue5259.dir/bug.go b/test/fixedbugs/issue5259.dir/bug.go
new file mode 100644
index 0000000000..8512461686
--- /dev/null
+++ b/test/fixedbugs/issue5259.dir/bug.go
@@ -0,0 +1,17 @@
+// 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.
+
+package bug
+
+type S struct {
+	F func()
+}
+
+type X interface {
+	Bar()
+}
+
+func Foo(x X) *S {
+	return &S{F: x.Bar}
+}
diff --git a/test/fixedbugs/issue5259.dir/main.go b/test/fixedbugs/issue5259.dir/main.go
new file mode 100644
index 0000000000..ad1da78f5f
--- /dev/null
+++ b/test/fixedbugs/issue5259.dir/main.go
@@ -0,0 +1,16 @@
+// 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.
+
+package main
+
+import "./bug"
+
+type foo int
+
+func (f *foo) Bar() {
+}
+
+func main() {
+	bug.Foo(new(foo))
+}
diff --git a/test/fixedbugs/issue5259.go b/test/fixedbugs/issue5259.go
new file mode 100644
index 0000000000..00fe19ff94
--- /dev/null
+++ b/test/fixedbugs/issue5259.go
@@ -0,0 +1,9 @@
+// compiledir
+
+// 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 5259: Inlining of method value causes internal compiler error
+
+package ignored

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/7b8e08617ea0d2b119766e0fd893fbf4502280e8

元コミット内容

このコミットは、Goコンパイラ(cmd/gc)において、メソッド値のインライン化を無効にするものです。その理由は、メソッド値のインライン化が内部コンパイラエラー(ICE)を引き起こす可能性があり、また、その処理コストが高いため、インライン化による性能上のメリットが少ないと判断されたためです。この変更は、GoのIssue 5259を修正します。

変更の背景

この変更の背景には、Goコンパイラが特定の状況下でメソッド値(method value)をインライン化しようとした際に発生する内部コンパイラエラー(ICE)がありました。Issue 5259は、この問題が報告されたバグトラッカーのエントリです。

メソッド値とは、Goにおいてメソッドを関数として扱うことができる機能です。例えば、obj.Methodのように記述することで、objMethodを呼び出す関数値を得ることができます。この関数値は、通常の関数と同様に変数に代入したり、引数として渡したりすることが可能です。

コンパイラは、プログラムの実行速度を向上させるために、頻繁に呼び出される小さな関数を呼び出し元に直接展開する「インライン化」という最適化を行います。しかし、メソッド値は通常の関数呼び出しとは異なる特性を持つため、そのインライン化のロジックが複雑になり、当時のGoコンパイラでは未対応のケースやバグが存在していました。

このコミットは、メソッド値のインライン化がICEを引き起こすという具体的なバグ報告(Issue 5259)に対応するものです。さらに、コミットメッセージには「それらは内部コンパイラエラーを引き起こし、インライン化する意味がないほどコストが高い」と明記されており、単なるバグ修正だけでなく、性能的な観点からもメソッド値のインライン化が適切ではないという判断が下されたことが示唆されています。

前提知識の解説

Goコンパイラ (cmd/gc)

Go言語の公式コンパイラは、主にcmd/gcとして知られています。これはGo言語で書かれたソースコードを機械語に変換する役割を担っています。cmd/gcは、最適化、型チェック、コード生成など、コンパイルプロセスの様々な段階を実行します。このコミットで変更されたsrc/cmd/gc/inl.cは、コンパイラのインライン化最適化に関連する部分です。

インライン化 (Inlining)

インライン化は、コンパイラ最適化の一種です。関数呼び出しのオーバーヘッド(スタックフレームの作成、引数の渡し、戻り値の処理など)を削減するために、呼び出される関数の本体を呼び出し元のコードに直接埋め込む(インライン展開する)技術です。これにより、関数呼び出しのコストがなくなるため、プログラムの実行速度が向上する可能性があります。しかし、インライン化しすぎると、生成されるバイナリのサイズが大きくなり、キャッシュの効率が悪化するなどのデメリットもあります。コンパイラは、インライン化の対象となる関数を、そのサイズや複雑さに基づいて慎重に選択します。

メソッド値 (Method Value)

Go言語では、構造体やインターフェースのメソッドを「メソッド値」として取得し、通常の関数のように扱うことができます。

type MyStruct struct {
    value int
}

func (m *MyStruct) GetValue() int {
    return m.value
}

func main() {
    m := &MyStruct{value: 10}
    f := m.GetValue // GetValueメソッドを関数値として取得
    println(f())    // 関数fを呼び出す
}

この例では、m.GetValueMyStructのインスタンスmにバインドされたGetValueメソッドを表す関数値です。この関数値は、func() int型の変数fに代入され、その後f()として呼び出されています。メソッド値は、クロージャの一種と考えることもできます。

OCALLPART

Goコンパイラの内部では、プログラムの構文木(AST: Abstract Syntax Tree)を表現するために様々なノードタイプが定義されています。OCALLPARTは、このようなノードタイプの一つで、メソッド値の生成(部分適用された関数呼び出し)を表します。例えば、上記のm.GetValueのような式は、コンパイラ内部でOCALLPARTノードとして表現されます。

ishairy 関数

src/cmd/gc/inl.cにあるishairy関数は、Goコンパイラのインライン化ロジックの一部です。この関数は、与えられたASTノード(Goのコードの断片)が「複雑すぎる(hairy)」かどうかを判断し、インライン化の対象から除外すべきかどうかを決定します。複雑なコードをインライン化すると、コンパイラの処理が遅くなったり、生成されるコードが肥大化したりする可能性があるため、このようなチェックが行われます。ishairy関数は、ノードの種類(Node->Op)に基づいて、そのノードがインライン化に適しているかを評価します。

技術的詳細

このコミットの技術的な核心は、src/cmd/gc/inl.cファイル内のishairy関数にOCALLPARTケースを追加した点です。

ishairy関数は、Goコンパイラが関数をインライン化するかどうかを決定する際に使用するヒューリスティックの一部です。この関数は、特定の種類のASTノード(Node->Opで識別される)が、インライン化するには複雑すぎる、または問題を引き起こす可能性があると判断した場合に、インライン化の「予算(budget)」を消費したり、インライン化を完全に禁止したりします。

変更前のishairy関数は、OCLOSURE(クロージャ)ノードを複雑なものとして扱っていました。メソッド値(m.GetValueのような式)は、内部的にはクロージャに似た構造を持つため、OCALLPARTノードとして表現されます。

このコミットでは、OCALLPARTノードがishairy関数によって「複雑な」ものとして扱われるように変更されました。具体的には、OCLOSUREケースの直後にcase OCALLPART:が追加されています。これにより、コンパイラはメソッド値の生成を表すOCALLPARTノードを含む関数をインライン化の対象から除外するようになります。

この変更の理由は、コミットメッセージに明記されている通り、メソッド値のインライン化が「内部コンパイラエラーを引き起こした」ためです。これは、当時のGoコンパイラのインライン化ロジックが、OCALLPARTノードが表現する特定のコードパターンを正しく処理できなかったことを示唆しています。また、「インライン化する意味がないほどコストが高い」という記述は、たとえバグが修正されたとしても、メソッド値のインライン化が性能上のメリットをもたらさない、あるいはむしろデメリットになる可能性が高いという判断があったことを示しています。

したがって、この変更は、特定のバグ(Issue 5259)を修正し、Goコンパイラの安定性を向上させるとともに、非効率な最適化を避けるための実用的な判断に基づいています。

コアとなるコードの変更箇所

src/cmd/gc/inl.cの変更箇所は以下の通りです。

--- a/src/cmd/gc/inl.c
+++ b/src/cmd/gc/inl.c
@@ -188,6 +188,7 @@ ishairy(Node *n, int *budget)
 		break;
 
 	case OCLOSURE:
+	case OCALLPART:
 	case ORANGE:
 	case OFOR:
 	case OSELECT:

この差分は、ishairy関数内のswitch文にOCALLPARTケースが追加されたことを示しています。

コアとなるコードの解説

ishairy関数は、Goコンパイラのインライン化判断ロジックにおいて、特定のASTノードがインライン化に適さない「複雑な」構造を持っているかをチェックします。

変更前のコードでは、OCLOSURE(クロージャ)ノードがこの「複雑な」カテゴリに含まれていました。クロージャは、外部スコープの変数をキャプチャするため、その実装は通常の関数よりも複雑になりがちです。

このコミットで追加されたcase OCALLPART:は、OCLOSUREと同様に、OCALLPARTノードもインライン化の対象から除外すべきであるとコンパイラに指示します。OCALLPARTは、前述の通りメソッド値の生成を表すノードです。メソッド値は、レシーバ(m in m.GetValue)とメソッド自体を組み合わせた関数ポインタのようなものであり、内部的にはクロージャに似た複雑さを持つことがあります。

この変更により、ishairy関数はOCALLPARTノードに遭遇すると、インライン化の予算を消費するか、インライン化を完全に禁止するロジック(このコードスニペットには示されていませんが、ishairy関数の他の部分で実装されています)が適用されます。結果として、メソッド値を含む関数はインライン化されなくなり、Issue 5259で報告された内部コンパイラエラーが回避されます。

この修正は、コンパイラの安定性を高めるための防御的な措置であり、同時に、メソッド値のインライン化が性能的にメリットが少ないという判断に基づいています。

関連リンク

参考にした情報源リンク

  • Go Issue 5259のGitHubページ
  • Go CL 8636043のGo Code Reviewページ
  • Go言語のメソッド値に関する公式ドキュメントやチュートリアル(一般的な知識として)
  • コンパイラのインライン化最適化に関する一般的な情報(一般的な知識として)
  • Goコンパイラのソースコード(src/cmd/gc/inl.c)の分析