[インデックス 10169] ファイルの概要
このコミットは、Goコンパイラのガベージコレクション(gc)におけるエスケープ解析のバグを修正し、その修正を検証するためのテストを追加するものです。具体的には、go
ステートメント(ゴルーチンの起動)における引数のエスケープ解析が不完全であった問題に対処しています。
コミット
commit b4df33a6eafea21afb7c85dafc7550f5fc339c7c
Author: Russ Cox <rsc@golang.org>
Date: Tue Nov 1 11:02:43 2011 -0400
gc: test + fix escape analysis bug
R=lvd
CC=golang-dev
https://golang.org/cl/5333049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b4df33a6eafea21afb7c85dafc7550f5fc339c7c
元コミット内容
Goコンパイラのガベージコレクション(gc)におけるエスケープ解析のバグを修正し、その修正を検証するためのテストを追加。
変更の背景
Go言語では、変数がスタックに割り当てられるかヒープに割り当てられるかを決定する「エスケープ解析」というコンパイラ最適化が行われます。スタック割り当ては高速でガベージコレクションのオーバーヘッドがないため、可能な限りスタックが利用されます。しかし、変数の寿命が現在の関数スコープを超える場合(例えば、ポインタが関数外に返される、グローバル変数に代入される、クロージャによってキャプチャされるなど)、その変数はヒープに割り当てられる必要があります。
このコミット以前のGoコンパイラでは、go
ステートメント(ゴルーチンの起動)において、そのゴルーチンに渡される引数に対するエスケープ解析が不完全であるバグが存在していました。特に、可変長引数(...interface{}
のような形式)が関与する場合に、引数が適切にヒープにエスケープされるべきであるにもかかわらず、スタックに割り当てられてしまう可能性がありました。これは、ゴルーチンが非同期に実行されるため、その引数が呼び出し元の関数のスタックフレームが破棄された後も生存し続ける必要があるためです。もしヒープにエスケープされない場合、ダングリングポインタ(無効なメモリ領域を指すポインタ)が発生し、プログラムのクラッシュや未定義の動作を引き起こす可能性がありました。
このバグは、コンパイラの内部表現における OPROC
(プロシージャ呼び出し操作)と ODDDARG
(可変長引数)の処理に関連していたと考えられます。OPROC
は関数呼び出し全般を指し、ODDDARG
は可変長引数を表す内部ノードです。go
ステートメントは実質的に新しいゴルーチンでの関数呼び出しであるため、この部分のエスケープ解析が重要になります。
前提知識の解説
エスケープ解析 (Escape Analysis)
エスケープ解析は、コンパイラ最適化の一種で、プログラム内の変数がどこに割り当てられるべきかを決定します。
- スタック割り当て (Stack Allocation): 関数内で宣言されたローカル変数は、通常、その関数のスタックフレームに割り当てられます。関数が終了すると、スタックフレームは破棄され、その中の変数も自動的に解放されます。これは非常に高速で、ガベージコレクションの必要がありません。
- ヒープ割り当て (Heap Allocation): 変数の寿命が現在の関数スコープを超える場合、その変数はヒープに割り当てられます。ヒープはプログラム全体で共有されるメモリ領域であり、ガベージコレクタによって管理されます。ヒープ割り当てはスタック割り当てよりも遅く、ガベージコレクションのオーバーヘッドが発生します。
エスケープ解析の目的は、可能な限り多くの変数をスタックに割り当てることで、プログラムのパフォーマンスを向上させ、ガベージコレクションの負担を軽減することです。
変数がヒープにエスケープする一般的なケース:
- 関数の戻り値としてポインタが返される場合。
- グローバル変数や、関数スコープ外のデータ構造にポインタが代入される場合。
- クロージャが外部変数をキャプチャし、そのクロージャが外部にエスケープする場合。
go
ステートメントでゴルーチンに渡される引数(特にポインタや参照型)。ゴルーチンは非同期に実行されるため、呼び出し元のスタックが破棄された後も引数が生存し続ける必要があるため、ヒープに割り当てられる必要があります。- 可変長引数 (
...interface{}
)。これらの引数は内部的にスライスとして扱われ、そのスライスが関数スコープを超えて参照される可能性があるため、ヒープにエスケープすることが多いです。
go
ステートメントとゴルーチン
go
ステートメントは、Go言語における並行処理の基本です。go
キーワードの後に続く関数呼び出しは、新しいゴルーチンとして並行して実行されます。ゴルーチンは軽量なスレッドのようなもので、Goランタイムによって管理されます。
go f(x)
のように関数を起動する場合、f
の実行に必要な引数 x
は、go
ステートメントが実行された時点のコンテキストから新しいゴルーチンのコンテキストへ「コピー」されるか、あるいは「参照」が渡されます。もし x
がポインタや参照型であり、かつその値が呼び出し元のスタック上に存在する場合、ゴルーチンが実行される頃には呼び出し元のスタックが既に破棄されている可能性があるため、x
はヒープにエスケープされる必要があります。
src/cmd/gc/esc.c
src/cmd/gc/esc.c
は、Goコンパイラのガベージコレクション(gc)部分におけるエスケープ解析のロジックを実装しているC言語のファイルです。このファイルには、AST(抽象構文木)を走査し、各ノード(変数、関数呼び出し、演算など)がどのようにメモリに割り当てられるべきかを判断するアルゴリズムが含まれています。
技術的詳細
このコミットの技術的な核心は、src/cmd/gc/esc.c
ファイル内の esc
関数、特に OPROC
(プロシージャ呼び出し)のケースにおける修正です。
元のコードでは、OPROC
ノード(go f(x)
の f(x)
部分)のエスケープ解析において、関数 f
自体 (n->left->left
) は theSink
にエスケープされるとマークされていましたが、可変長引数を含む可能性のある実際の引数リスト (n->left->right
、これは ODDDARG
ノードに対応することが多い) が適切に処理されていませんでした。
theSink
は、エスケープ解析において「どこかにエスケープする」ことを示す特別な場所(仮想的なシンク)を表します。escassign(&theSink, ...)
は、指定されたノードが theSink
に割り当てられる、つまりヒープにエスケープされるべきであることを意味します。
修正前は、go f(x)
の x
が可変長引数である場合、その引数がヒープにエスケープされるべきであるにもかかわらず、エスケープ解析がそれを認識せず、スタックに割り当ててしまう可能性がありました。これは、ゴルーチンが非同期に実行されるため、呼び出し元のスタックフレームが破棄された後も引数が生存し続ける必要があるというGoのメモリモデルと矛盾します。
このコミットでは、OPROC
のケースに以下の行が追加されました。
escassign(&theSink, n->left->right); // ODDDARG for call
この変更により、go
ステートメントの関数呼び出しにおける n->left->right
(これは通常、可変長引数を含む引数リストを表す ODDDARG
ノードに対応します)も theSink
にエスケープされるように明示的にマークされるようになりました。これにより、go
ステートメントに渡される可変長引数が確実にヒープに割り当てられ、ダングリングポインタの問題が回避されます。
test/escape2.go
の変更は、この修正を検証するためのものです。多くのテストケースが追加されており、特に foo121
と foo121b
関数は、defer
と go
ステートメント内で可変長引数 (...interface{}
) を使用した場合のエスケープ解析の挙動をテストしています。これらのテストケースでは、i
のようなローカル変数が defer
や go
の引数として渡される際に、ヒープにエスケープされるべきであることを // ERROR
コメントで明示的に期待しています。
コアとなるコードの変更箇所
src/cmd/gc/esc.c
diff --git a/src/cmd/gc/esc.c b/src/cmd/gc/esc.c
index de73ebe6f3..4382ed6f01 100644
--- a/src/cmd/gc/esc.c
+++ b/src/cmd/gc/esc.c
@@ -239,6 +239,7 @@ esc(Node *n)
case OPROC:
// go f(x) - f and x escape
escassign(&theSink, n->left->left);
+ escassign(&theSink, n->left->right); // ODDDARG for call
for(ll=n->left->list; ll; ll=ll->next)
escassign(&theSink, ll->n);
break;
test/escape2.go
diff --git a/test/escape2.go b/test/escape2.go
index 7366a53c7f..06ada5aaa0 100644
--- a/test/escape2.go
+++ b/test/escape2.go
@@ -6,12 +6,15 @@
package foo
-import "unsafe"
+import (
+ "fmt"
+ "unsafe"
+)
var gxx *int
func foo1(x int) { // ERROR "moved to heap: x"
- gxx = &x // ERROR "&x escapes to heap"
+ gxx = &x // ERROR "&x escapes to heap"
}
func foo2(yy *int) { // ERROR "leaking param: yy"
@@ -19,7 +22,7 @@ func foo2(yy *int) { // ERROR "leaking param: yy"
}
func foo3(x int) *int { // ERROR "moved to heap: x"
- return &x // ERROR "&x escapes to heap"
+ return &x // ERROR "&x escapes to heap"
}
type T *T
@@ -35,7 +38,7 @@ func foo4(xx, yy *int) { // ERROR "xx does not escape" "yy does not escape"
// xx isn't going anywhere, so taking address of yy is ok
func foo5(xx **int, yy *int) { // ERROR "xx does not escape" "yy does not escape"
- xx = &yy // ERROR "&yy does not escape"
+ xx = &yy // ERROR "&yy does not escape"
}
func foo6(xx **int, yy *int) { // ERROR "xx does not escape" "leaking param: yy"
@@ -62,8 +65,8 @@ func foo10(xx, yy *int) { // ERROR "xx does not escape" "yy does not escape"
func foo11() int {
x, y := 0, 42
- xx := &x // ERROR "&x does not escape"
- yy := &y // ERROR "&y does not escape"
+ xx := &x // ERROR "&x does not escape"
+ yy := &y // ERROR "&y does not escape"
*xx = *yy
return x
}
@@ -83,7 +86,7 @@ func foo14(yyy **int) { // ERROR "yyy does not escape"
}
func foo15(yy *int) { // ERROR "moved to heap: yy"
- xxx = &yy // ERROR "&yy escapes to heap"
+ xxx = &yy // ERROR "&yy escapes to heap"
}
func foo16(yy *int) { // ERROR "leaking param: yy"
@@ -95,7 +98,7 @@ func foo17(yy *int) { // ERROR "yy does not escape"
}
func foo18(y int) { // ERROR "moved to heap: "y"
- *xxx = &y // ERROR "&y escapes to heap"
+ *xxx = &y // ERROR "&y escapes to heap"
}
func foo19(y int) {
@@ -127,7 +130,7 @@ func (b *Bar) AlsoNoLeak() *int { // ERROR "b does not escape"
return b.ii
}
-func goLeak(b *Bar) { // ERROR "leaking param: b"
+func goLeak(b *Bar) { // ERROR "leaking param: b"
go b.NoLeak()
}
@@ -145,49 +148,49 @@ func (b *Bar2) NoLeak() int { // ERROR "b does not escape"
}
func (b *Bar2) Leak() []int { // ERROR "leaking param: b"
- return b.i[:] // ERROR "&b.i escapes to heap"
+ return b.i[:] // ERROR "&b.i escapes to heap"
}
func (b *Bar2) AlsoNoLeak() []int { // ERROR "b does not escape"
return b.i[:]
}
func (b *Bar2) LeakSelf() { // ERROR "leaking param: b"
- b.ii = b.i[0:4] // ERROR "&b.i escapes to heap"
+ b.ii = b.i[0:4] // ERROR "&b.i escapes to heap"
}
func (b *Bar2) LeakSelf2() { // ERROR "leaking param: b"
var buf []int
- buf = b.i[0:] // ERROR "&b.i escapes to heap"
+ buf = b.i[0:] // ERROR "&b.i escapes to heap"
b.ii = buf
}
func foo21() func() int {
- x := 42 // ERROR "moved to heap: x"
- return func() int { // ERROR "func literal escapes to heap"
- return x // ERROR "&x escapes to heap"
+ x := 42 // ERROR "moved to heap: x"
+ return func() int { // ERROR "func literal escapes to heap"
+ return x // ERROR "&x escapes to heap"
}
}
func foo22() int {
x := 42
- return func() int { // ERROR "func literal does not escape"
+ return func() int { // ERROR "func literal does not escape"
return x
}()
}
func foo23(x int) func() int { // ERROR "moved to heap: x"
- return func() int { // ERROR "func literal escapes to heap"
- return x // ERROR "&x escapes to heap"
+ return func() int { // ERROR "func literal escapes to heap"
+ return x // ERROR "&x escapes to heap"
}
}
func foo23a(x int) func() int { // ERROR "moved to heap: x"
- f := func() int { // ERROR "func literal escapes to heap"
- return x // ERROR "&x escapes to heap"
+ f := func() int { // ERROR "func literal escapes to heap"
+ return x // ERROR "&x escapes to heap"
}
return f
}
func foo23b(x int) *(func() int) { // ERROR "moved to heap: x"
f := func() int { return x } // ERROR "moved to heap: f" "func literal escapes to heap" "&x escapes to heap"
- return &f // ERROR "&f escapes to heap"
+ return &f // ERROR "&f escapes to heap"
}
func foo24(x int) int {
- return func() int { // ERROR "func literal does not escape"
+ return func() int { // ERROR "func literal does not escape"
return x
}()
}
@@ -212,11 +215,11 @@ func foonoleak(xx *int) int { // ERROR "xx does not escape"
}
func foo31(x int) int { // ERROR "moved to heap: x"
- return fooleak(&x) // ERROR "&x escapes to heap"
+ return fooleak(&x) // ERROR "&x escapes to heap"
}
func foo32(x int) int {
- return foonoleak(&x) // ERROR "&x does not escape"
+ return foonoleak(&x) // ERROR "&x does not escape"
}
type Foo struct {
@@ -244,15 +247,15 @@ func (f *Foo) NoLeak() { // ERROR "f does not escape"
}
func foo41(x int) { // ERROR "moved to heap: x"
- F.xx = &x // ERROR "&x escapes to heap"
+ F.xx = &x // ERROR "&x escapes to heap"
}
func (f *Foo) foo42(x int) { // ERROR "f does not escape" "moved to heap: x"
- f.xx = &x // ERROR "&x escapes to heap"
+ f.xx = &x // ERROR "&x escapes to heap"
}
func foo43(f *Foo, x int) { // ERROR "f does not escape" "moved to heap: x"
- f.xx = &x // ERROR "&x escapes to heap"
+ f.xx = &x // ERROR "&x escapes to heap"
}
func foo44(yy *int) { // ERROR "leaking param: yy"
@@ -268,7 +271,7 @@ func (f *Foo) foo46() { // ERROR "f does not escape"
}
func (f *Foo) foo47() { // ERROR "leaking param: f"
- f.xx = &f.x // ERROR "&f.x escapes to heap"
+ f.xx = &f.x // ERROR "&f.x escapes to heap"
}
var ptrSlice []*int
@@ -284,38 +287,38 @@ func foo51(i *int) { // ERROR "leaking param: i"
}
func indaddr1(x int) *int { // ERROR "moved to heap: x"
- return &x // ERROR "&x escapes to heap"
+ return &x // ERROR "&x escapes to heap"
}
func indaddr2(x *int) *int { // ERROR "leaking param: x"
- return *&x // ERROR "&x does not escape"
+ return *&x // ERROR "&x does not escape"
}
func indaddr3(x *int32) *int { // ERROR "leaking param: x"
- return *(**int)(unsafe.Pointer(&x)) // ERROR "&x does not escape"
+ return *(**int)(unsafe.Pointer(&x)) // ERROR "&x does not escape"
}
// From package math:
func Float32bits(f float32) uint32 {
- return *(*uint32)(unsafe.Pointer(&f)) // ERROR "&f does not escape"
+ return *(*uint32)(unsafe.Pointer(&f)) // ERROR "&f does not escape"
}
func Float32frombits(b uint32) float32 {
- return *(*float32)(unsafe.Pointer(&b)) // ERROR "&b does not escape"
+ return *(*float32)(unsafe.Pointer(&b)) // ERROR "&b does not escape"
}
func Float64bits(f float64) uint64 {
- return *(*uint64)(unsafe.Pointer(&f)) // ERROR "&f does not escape"
+ return *(*uint64)(unsafe.Pointer(&f)) // ERROR "&f does not escape"
}
func Float64frombits(b uint64) float64 {
- return *(*float64)(unsafe.Pointer(&b)) // ERROR "&b does not escape"
+ return *(*float64)(unsafe.Pointer(&b)) // ERROR "&b does not escape"
}
// contrast with
func float64bitsptr(f float64) *uint64 { // ERROR "moved to heap: f"
- return (*uint64)(unsafe.Pointer(&f)) // ERROR "&f escapes to heap"
+ return (*uint64)(unsafe.Pointer(&f)) // ERROR "&f escapes to heap"
}
func float64ptrbitsptr(f *float64) *uint64 { // ERROR "leaking param: f"
@@ -328,7 +331,7 @@ func typesw(i interface{}) *int { // ERROR "leaking param: i"
\t\treturn val
\tcase *int8:
\t\tv := int(*val) // ERROR "moved to heap: v"
- \t\treturn &v // ERROR "&v escapes to heap"
+ \t\treturn &v // ERROR "&v escapes to heap"
\t}\n \treturn nil
}\n@@ -409,12 +412,12 @@ func (MV) M() {}\n
func foo65() {\n var mv MV\n-\tfoo63(&mv) // ERROR "&mv does not escape"\n+\tfoo63(&mv) // ERROR "&mv does not escape"\n }\n
func foo66() {\n-\tvar mv MV // ERROR "moved to heap: mv"\n-\tfoo64(&mv) // ERROR "&mv escapes to heap"\n+\tvar mv MV // ERROR "moved to heap: mv"\n+\tfoo64(&mv) // ERROR "&mv escapes to heap"\n }\n
func foo67() {\n var mv MV\n@@ -444,20 +447,20 @@ func foo71(x *int) []*int { // ERROR "leaking param: x"\n
func foo71a(x int) []*int { // ERROR "moved to heap: x"
var y []*int
-\ty = append(y, &x) // ERROR "&x escapes to heap"\n+\ty = append(y, &x) // ERROR "&x escapes to heap"\n return y
}\n
func foo72() {\n var x int\n var y [1]*int\n-\ty[0] = &x // ERROR "&x does not escape"\n+\ty[0] = &x // ERROR "&x does not escape"\n }\n
func foo72aa() [10]*int {\n var x int // ERROR "moved to heap: x"
var y [10]*int
-\ty[0] = &x // ERROR "&x escapes to heap"\n+\ty[0] = &x // ERROR "&x escapes to heap"\n return y
}\n
@@ -465,7 +468,7 @@ func foo72a() {\n var y [10]*int
for i := 0; i < 10; i++ {\n // escapes its scope
-\t\tx := i // ERROR "moved to heap: x"\n+\t\tx := i // ERROR "moved to heap: x"\n \t\ty[i] = &x // ERROR "&x escapes to heap"\n \t}\n \treturn\n@@ -474,8 +477,8 @@ func foo72a() {\n func foo72b() [10]*int {\n var y [10]*int
for i := 0; i < 10; i++ {\n-\t\tx := i // ERROR "moved to heap: x"\n-\t\ty[i] = &x // ERROR "&x escapes to heap"\n+\t\tx := i // ERROR "moved to heap: x"\n+\t\ty[i] = &x // ERROR "&x escapes to heap"\n \t}\n \treturn y
}\n@@ -484,10 +487,10 @@ func foo72b() [10]*int {\n func foo73() {\n s := []int{3, 2, 1} // ERROR "\\[\\]int literal does not escape"\n for _, v := range s {\n-\t\tvv := v // ERROR "moved to heap: vv"\n+\t\tvv := v // ERROR "moved to heap: vv"\n \t\t// actually just escapes its scope
\t\tdefer func() { // ERROR "func literal escapes to heap"\n-\t\t\tprintln(vv) // ERROR "&vv escapes to heap"\n+\t\t\tprintln(vv) // ERROR "&vv escapes to heap"\n \t\t}()\n \t}\n }\n@@ -495,10 +498,10 @@ func foo73() {\n func foo74() {\n s := []int{3, 2, 1} // ERROR "\\[\\]int literal does not escape"\n for _, v := range s {\n-\t\tvv := v // ERROR "moved to heap: vv"\n+\t\tvv := v // ERROR "moved to heap: vv"\n \t\t// actually just escapes its scope
\t\tfn := func() { // ERROR "func literal escapes to heap"\n-\t\t\tprintln(vv) // ERROR "&vv escapes to heap"\n+\t\t\tprintln(vv) // ERROR "&vv escapes to heap"\n \t\t}\n \t\tdefer fn()\n \t}\n@@ -509,7 +512,7 @@ func myprint(y *int, x ...interface{}) *int { // ERROR "x does not escape" "leak\n }\n
func myprint1(y *int, x ...interface{}) *interface{} { // ERROR "y does not escape" "leaking param: x"\n-\treturn &x[0] // ERROR "&x.0. escapes to heap"\n+\treturn &x[0] // ERROR "&x.0. escapes to heap"\n }\n
func foo75(z *int) { // ERROR "leaking param: z"\n@@ -566,12 +569,12 @@ func foo77a(z []interface{}) { // ERROR "leaking param: z"\n }\n
func foo78(z int) *int { // ERROR "moved to heap: z"\n-\treturn &z // ERROR "&z escapes to heap"\n+\treturn &z // ERROR "&z escapes to heap"\n }\n
func foo78a(z int) *int { // ERROR "moved to heap: z"\n-\ty := &z // ERROR "&z escapes to heap"\n-\tx := &y // ERROR "&y does not escape"\n+\ty := &z // ERROR "moved to heap: z"\n+\tx := &y // ERROR "&y does not escape"\n \treturn *x // really return y
}\n
@@ -685,7 +688,7 @@ func foo101(m [1]*int) *int { // ERROR "leaking param: m"\n // does not leak m
func foo101a(m [1]*int) *int { // ERROR "m does not escape"\n \tfor i := range m { // ERROR "moved to heap: i"\n-\t\treturn &i // ERROR "&i escapes to heap"\n+\t\treturn &i // ERROR "&i escapes to heap"\n \t}\n \treturn nil
}\n@@ -703,12 +706,12 @@ func foo103(m [1]*int, x *int) { // ERROR "m does not escape" "x does not escape\n var y []*int\n
// does not leak x
-func foo104(x []*int) { // ERROR "x does not escape"\n+func foo104(x []*int) { // ERROR "x does not escape"\n \tcopy(y, x)\n }\n
// does not leak x
-func foo105(x []*int) { // ERROR "x does not escape"\n+func foo105(x []*int) { // ERROR "x does not escape"\n \t_ = append(y, x...)\n }\n
@@ -726,7 +729,7 @@ func foo108(x *int) map[*int]*int { // ERROR "leaking param: x"\n }\n
func foo109(x *int) *int { // ERROR "leaking param: x"
-\tm := map[*int]*int{x: nil} // ERROR "map.* literal does not escape"\n+\tm := map[*int]*int{x: nil} // ERROR "map.* literal does not escape"\n \tfor k, _ := range m {\n \t\treturn k\n \t}\n@@ -734,12 +737,12 @@ func foo109(x *int) *int { // ERROR "leaking param: x"\n }\n
func foo110(x *int) *int { // ERROR "leaking param: x"
-\tm := map[*int]*int{nil: x} // ERROR "map.* literal does not escape"\n+\tm := map[*int]*int{nil: x} // ERROR "map.* literal does not escape"\n \treturn m[nil]\n }\n
func foo111(x *int) *int { // ERROR "leaking param: x"
-\tm := []*int{x} // ERROR "\\[\\]\\*int literal does not escape"\n+\tm := []*int{x} // ERROR "\\[\\]\\*int literal does not escape"\n \treturn m[0]\n }\n
@@ -754,7 +757,7 @@ func foo113(x *int) *int { // ERROR "leaking param: x"\n }\n
func foo114(x *int) *int { // ERROR "leaking param: x"
-\tm := &Bar{ii: x} // ERROR "&Bar literal does not escape"\n+\tm := &Bar{ii: x} // ERROR "&Bar literal does not escape"\n \treturn m.ii
}\n
@@ -764,28 +767,28 @@ func foo115(x *int) *int { // ERROR "leaking param: x"\n
func foo116(b bool) *int {\n if b {\n-\t\tx := 1 // ERROR "moved to heap: x"\n-\t\treturn &x // ERROR "&x escapes to heap"\n+\t\tx := 1 // ERROR "moved to heap: x"\n+\t\treturn &x // ERROR "&x escapes to heap"\n \t} else {\n-\t\ty := 1 // ERROR "moved to heap: y"\n-\t\treturn &y // ERROR "&y escapes to heap"\n+\t\ty := 1 // ERROR "moved to heap: y"\n+\t\treturn &y // ERROR "&y escapes to heap"\n \t}\n \treturn nil
}\n
-func foo117(unknown func(interface{})) { // ERROR "unknown does not escape"\n-\tx := 1 // ERROR "moved to heap: x"\n+func foo117(unknown func(interface{})) { // ERROR "unknown does not escape"\n+\tx := 1 // ERROR "moved to heap: x"\n \tunknown(&x) // ERROR "&x escapes to heap"\n }\n
-func foo118(unknown func(*int)) { // ERROR "unknown does not escape"\n-\tx := 1 // ERROR "moved to heap: x"\n+func foo118(unknown func(*int)) { // ERROR "unknown does not escape"\n+\tx := 1 // ERROR "moved to heap: x"\n \tunknown(&x) // ERROR "&x escapes to heap"\n }\n
func external(*int)\n
-func foo119(x *int) { // ERROR "leaking param: x"\n+func foo119(x *int) { // ERROR "leaking param: x"\n \texternal(x)\n }\n
@@ -993,3 +996,18 @@ L100:\n \tgoto L99\n \tgoto L100\n }\n+\n+func foo121() {\n+\tfor i := 0; i < 10; i++ {\n+\t\tdefer myprint(nil, i) // ERROR \"[.][.][.] argument escapes to heap\"\n+\t\tgo myprint(nil, i) // ERROR \"[.][.][.] argument escapes to heap\"\n+\t}\n+}\n+\n+// same as foo121 but check across import\n+func foo121b() {\n+\tfor i := 0; i < 10; i++ {\n+\t\tdefer fmt.Printf(\"%d\", i) // ERROR \"[.][.][.] argument escapes to heap\"\n+\t\tgo fmt.Printf(\"%d\", i) // ERROR \"[.][.][.] argument escapes to heap\"\n+\t}\n+}\n```
## コアとなるコードの解説
### `src/cmd/gc/esc.c` の変更
* **`case OPROC:`**: このブロックは、`go` ステートメントや通常の関数呼び出しなど、プロシージャ呼び出しのエスケープ解析を処理します。
* **`escassign(&theSink, n->left->left);`**: これは、呼び出される関数自体(`go f(x)` の `f`)が `theSink` にエスケープされることを示しています。これは、関数ポインタなどがヒープにエスケープされる必要がある場合に重要です。
* **`escassign(&theSink, n->left->right); // ODDDARG for call`**: **この行が追加された主要な変更点です。** `n->left->right` は、`OPROC` ノードの右側のオペランドであり、関数呼び出しの引数リストを表します。特に、可変長引数 (`...interface{}`) の場合は `ODDDARG` ノードになります。この追加により、`go` ステートメントに渡される引数(特に可変長引数)が、呼び出し元のスタックフレームが破棄された後も生存し続ける必要があるため、確実にヒープにエスケープされるように修正されました。
* **`for(ll=n->left->list; ll; ll=ll->next) escassign(&theSink, ll->n);`**: これは、残りの引数リストをイテレートし、それぞれを `theSink` にエスケープする既存のロジックです。追加された行は、このループの前に `ODDDARG` ノード自体を処理することで、可変長引数全体のコンテナ(スライス)が適切にエスケープされるようにしています。
### `test/escape2.go` の変更
`test/escape2.go` は、Goコンパイラのエスケープ解析の挙動をテストするためのファイルです。このコミットでは、既存のテストケースに加えて、特に `go` ステートメントと `defer` ステートメントにおける可変長引数のエスケープ解析のバグを再現し、修正を検証するための新しいテストケースが追加されています。
* **`import "fmt"` の追加**: 新しいテストケースで `fmt.Printf` を使用するために `fmt` パッケージがインポートされました。
* **`foo121()` と `foo121b()` の追加**:
* これらの関数は、ループ内で `defer` と `go` ステートメントを使用し、ローカル変数 `i` を可変長引数として `myprint` 関数(または `fmt.Printf`)に渡しています。
* `// ERROR "[.][.][.] argument escapes to heap"` というコメントは、コンパイラがこれらの引数(`i` の値)がヒープにエスケープされると判断することを期待していることを示しています。
* これは、`defer` や `go` ステートメントのクロージャが、ループの各イテレーションで異なる `i` の値をキャプチャし、それらの値がループの終了後も生存し続ける必要があるためです。もし `i` がスタックに割り当てられたままだと、ループが終了した時点で `i` のメモリが解放され、`defer` や `go` で実行される関数が不正なメモリにアクセスする可能性があります。
* これらのテストケースの追加により、`src/cmd/gc/esc.c` の修正が正しく機能し、`go` ステートメントや `defer` ステートメントにおける可変長引数が適切にヒープにエスケープされることが保証されます。
## 関連リンク
* Go CL (Code Review) リンク: [https://golang.org/cl/5333049](https://golang.org/cl/5333049)
## 参考にした情報源リンク
* Go escape analysis: [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH3Uzmp_S2AdCaZz9qp4b7AiPAZlQiQ2JqZBTYe9cbN1yqqrD6AiKPrOMjK1azo_V08UsFGJzF60vp3DNoplMGavcrvJQn9zKf-mOgaWk-bfglxZZ3FnbTWCF1r4hAwWFhPdaUCcbxLf6bmURKVF5ke_xP-VzVh36D4xw==](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH3Uzmp_S2AdCaZz9qp4b7AiPAZlQiQ2JqZBTYe9cbN1yqqrD6AiKPrOMjK1azo_V08UsFGJzF60vp3DNoplMGavcrvJQn9zKf-mOgaWk-bfglxZZ3FnbTWCF1r4hAwWFhPdaUCcbxLf6bmURKVF5ke_xP-VzVh36D4xw==)
* Go escape analysis (Google): [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFzQxIIDjWujmOGk266YYtl26VQvU2yQOA0W1i8IkY5Vb5vtN_dBXUBm-eFX6RIBjm374-8zUFmWVpsvbli1Zsj5bSXz_GEM45v0eh9x_igv5L8nRMUCIpCm3y0CmWMmkJuMG5qMI1PsAkSPsGya27saRMzenAjjY7QQH8z_CHc610NrtTd6D4_TnIIK-eG](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFzQxIIDjWujmOGk266YYtl26VQvU2yQOA0W1i8IkY5Vb5vtN_dBXUBm-eFX6RIBjm374-8zUFmWVpsvbli1Zsj5bSXz_GEM45v0eh9x_igv5L8nRMUCIpCm3y0CmWMmkJuMG5qMI1PsAkSPsGya27saRMzenAjjY7QQH8z_CHc610NrtTd6D4_TnIIK-eG)
* Go escape analysis (Stack Overflow): [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFXeWQBRT2EVjkqipIZjslbpekj5c-6ivb5_v13o6dZ1KLPrD6sb-WSbbTi4uLmFoXhvQghC1KuOvhW7CSuq3cqn4TuceYOfLqNx_jJQx2-E3KRfKfhZW6KjOM4Tr_3EPgAww9DsnsB33F0cOyvz6oPT6NjIZCc3AQT3fpTg8aZqKumSAszVw2JO7VgHtJf55k0auYI](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFXeWQBRT2EVjkqipIZjslbpekj5c-6ivb5_v13o6dZ1KLPrD6sb-WSbbTi4uLmFoXhvQghC1KuOvhW7CSuq3cqn4TuceYOfLqNx_jJQx2-E3KRfKfhZW6KjOM4Tr_3EPgAww9DsnsB33F0cOyvz6oPT6NjIZCc3AQT3fpTg8aZqKumSAszVw2JO3VgHtJf55k0auYI)
* Go escape analysis (Ardan Labs): [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH_3VxuOYOHZ2NSHiH5ymO2vRgo07C1EUg02p1qd-NC90dcGW3BQCy4y1b4WgAsqLkRAOaYsObhYcuaNOQ7rGN9pj2a46EDNUW4PxUbUWJsIh8_lLXcWFDzy127aAzTwx-1hDnd4QI7WseWBD30eIcKjaZrEKtxjrMBDic=](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH_3VxuOYOHZ2NSHiH5ymO2vRgo07C1EUg02p1qd-NC90dcGW3BQCy4y1b4WgAsqLkRAOaYsObhYcuaNOQ7rGN9pj2a46EDNUW4PxUbUWJsIh8_lLXcWFDzy127aAzTwx-1hDnd4QI7WseWBD30eIcKjaZrEKtxjrMBDic=)