[インデックス 19305] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)におけるエスケープ解析のバグ修正に関するものです。特に、ポインタを含まない要素型を持つ可変引数(...
)が正しくエスケープ解析されない問題に対処しています。
コミット
cmd/gc: fix ... escape analysis bug
If the ... element type contained no pointers,
then the escape analysis did not track the ... itself.
This manifested in an escaping ...byte being treated
as non-escaping.
Fixes #7934.
LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/100310043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c99dce2b058b2260f05d694c1eaf0bbf16e79d27
元コミット内容
commit c99dce2b058b2260f05d694c1eaf0bbf16e79d27
Author: Russ Cox <rsc@golang.org>
Date: Fri May 9 15:40:45 2014 -0400
cmd/gc: fix ... escape analysis bug
If the ... element type contained no pointers,
then the escape analysis did not track the ... itself.
This manifested in an escaping ...byte being treated
as non-escaping.
Fixes #7934.
LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/100310043
---\n src/cmd/gc/esc.c | 2 ++\n src/cmd/gc/order.c | 2 +-\n test/escape2.go | 28 ++++++++++++++++++++--------\n 3 files changed, 23 insertions(+), 9 deletions(-)\n
変更の背景
Goコンパイラのエスケープ解析において、可変引数(...
)の要素型がポインタを含まない場合(例: ...byte
)、その可変引数自体が正しく追跡されないというバグが存在しました。この結果、本来ヒープにエスケープすべき...byte
型の値が、誤ってスタックに割り当てられると判断され、ガベージコレクションの対象外となる可能性がありました。これは、プログラムの誤動作やメモリリークに繋がる重大な問題です。
このコミットは、GitHub Issue #7934で報告されたこの問題を解決するために作成されました。エスケープ解析は、変数がプログラムのどの部分でアクセスされるかを分析し、その変数をスタックに割り当てるかヒープに割り当てるかを決定するコンパイラの重要な最適化機能です。このバグは、特に可変引数とポインタを含まない型との組み合わせで、この最適化が正しく機能しないことを示していました。
前提知識の解説
1. エスケープ解析 (Escape Analysis)
エスケープ解析は、コンパイラが行う最適化の一つで、変数がプログラムの実行中にどこに割り当てられるべきかを決定します。
- スタック割り当て (Stack Allocation): 関数内で宣言された変数が、その関数が終了するまでしか生存しない場合、通常はスタックに割り当てられます。スタックは高速で、ガベージコレクションのオーバーヘッドがありません。
- ヒープ割り当て (Heap Allocation): 変数が関数のスコープを超えて生存する必要がある場合(例: グローバル変数、他の関数に渡されるポインタ、クロージャによって参照される変数など)、ヒープに割り当てられます。ヒープはガベージコレクタによって管理され、スタックよりもアクセスが遅く、GCのオーバーヘッドが発生します。
エスケープ解析の目的は、可能な限り多くの変数をスタックに割り当てることで、プログラムのパフォーマンスを向上させ、ガベージコレクションの負担を軽減することです。
2. 可変引数 (Variadic Functions)
Go言語では、関数が不定数の引数を受け取ることができる「可変引数」という機能があります。これは、関数の最後のパラメータの型の前に...
を付けることで定義されます。
例: func sum(nums ...int)
この関数が呼び出されると、nums
は実際には[]int
型のスライスとして関数内で扱われます。例えば、sum(1, 2, 3)
と呼び出すと、関数内ではnums
が[]int{1, 2, 3}
として扱われます。
3. Goコンパイラの構造 (cmd/gc
)
cmd/gc
は、Go言語の公式コンパイラです。ソースコードを解析し、中間表現を生成し、最適化を行い、最終的に実行可能なバイナリを生成します。エスケープ解析は、このコンパイルプロセスの最適化フェーズの一部として実行されます。
4. Node
とType
Goコンパイラの内部では、プログラムの構造は抽象構文木(AST)として表現され、そのノードはNode
構造体で表されます。各ノードは、そのノードが表す式の型情報(Type
)を持っています。
typ(TARRAY)
: 配列型を表すType
を生成します。ptrto(Type)
: 指定されたType
へのポインタ型を表すType
を生成します。
技術的詳細
このバグは、可変引数(...
)がスライスとして扱われる際に、そのスライスの要素型がポインタを含まない場合に発生しました。具体的には、...byte
のような場合です。
エスケープ解析は、変数がポインタであるかどうか、またはポインタを含む構造体であるかどうかを追跡することで、その変数がヒープにエスケープするかどうかを判断します。しかし、...byte
のように要素型がbyte
(ポインタではない)である場合、コンパイラのエスケープ解析ロジックが、可変引数自体(つまり、[]byte
スライスヘッダ)がポインタとして扱われるべきであるという事実を見落としていました。
可変引数は、内部的にはスライスとして実装されます。このスライスヘッダは、基底配列へのポインタ、長さ、容量を含みます。たとえ要素型がポインタを含まなくても、このスライスヘッダ自体はポインタを含んでいるため、スライスが関数のスコープを超えて使用される場合(例: グローバル変数に代入される場合)、そのスライスヘッダはヒープにエスケープする必要があります。
このコミットの修正は、src/cmd/gc/esc.c
において、可変引数を表すNode
の型を明示的にポインタ型に変換することで、エスケープ解析がそのNode
を正しく追跡するように変更しました。これにより、...byte
のような可変引数も、そのスライスヘッダがヒープにエスケープする必要がある場合に正しく検出されるようになります。
また、src/cmd/gc/order.c
の変更は、可変引数の一時的な割り当てに関するものです。ordertemp
関数は一時変数を割り当てるために使用されますが、以前は可変引数全体の型(n->type
)を渡していました。しかし、可変引数はスライスであり、その実体は要素の配列です。この修正では、n->type->type
(つまり、スライスの要素型)を渡すことで、一時的な割り当てがより正確に要素の型に基づいて行われるようにしています。これは、エスケープ解析がスライスヘッダと基底配列の両方を正しく考慮するために必要な調整と考えられます。
test/escape2.go
の変更は、このバグを再現し、修正が正しく機能することを確認するための新しいテストケースを追加しています。foo150
関数は...byte
を受け取り、それをグローバル変数save150
に代入することで、x
がヒープにエスケープすることを意図しています。修正前は、このテストはエスケープを検出できませんでしたが、修正後は正しく検出されるようになります。
コアとなるコードの変更箇所
src/cmd/gc/esc.c
--- a/src/cmd/gc/esc.c
+++ b/src/cmd/gc/esc.c
@@ -903,6 +903,7 @@ esccall(EscState *e, Node *n, Node *up)
src->type = typ(TARRAY);
src->type->type = lr->n->type->type;
src->type->bound = count(ll);
+ src->type = ptrto(src->type); // make pointer so it will be tracked
src->escloopdepth = e->loopdepth;
src->lineno = n->lineno;
src->esc = EscNone; // until we find otherwise
@@ -960,6 +961,7 @@ esccall(EscState *e, Node *n, Node *up)
src->type = typ(TARRAY);
src->type->type = t->type->type;
src->type->bound = count(ll);
+ src->type = ptrto(src->type); // make pointer so it will be tracked
src->esc = EscNone; // until we find otherwise
e->noesc = list(e->noesc, src);
n->right = src;
src/cmd/gc/order.c
--- a/src/cmd/gc/order.c
+++ b/src/cmd/gc/order.c
@@ -1012,7 +1012,7 @@ orderexpr(Node **np, Order *order)
// Allocate a temporary that will be cleaned up when this statement
// completes. We could be more aggressive and try to arrange for it
// to be cleaned up when the call completes.
- n->alloc = ordertemp(n->type, order, 0);
+ n->alloc = ordertemp(n->type->type, order, 0);
}
break;
test/escape2.go
--- a/test/escape2.go
+++ b/test/escape2.go
@@ -1399,3 +1399,15 @@ func foo149(l List) { // ERROR " l does not escape"
}
}
}
+
+// issue 7934: missed ... if element type had no pointers
+
+var save150 []byte
+
+func foo150(x ...byte) { // ERROR "leaking param: x"
+ save150 = x
+}
+
+func bar150() {
+ foo150(1, 2, 3) // ERROR "[.]... argument escapes to heap"
+}
コアとなるコードの解説
src/cmd/gc/esc.c
の変更
esccall
関数は、関数呼び出しのエスケープ解析を行う部分です。可変引数(...
)が処理される際に、その引数を表すsrc
ノードの型が設定されます。
追加された行: src->type = ptrto(src->type); // make pointer so it will be tracked
この変更は、可変引数(内部的にはスライス)の型を、そのスライス型へのポインタ型に変換しています。
- 以前は、
src->type
は[]byte
のようなスライス型そのものでした。 - しかし、エスケープ解析のロジックは、ポインタ型の変数を追跡するように設計されています。
ptrto(src->type)
とすることで、src
ノードが*[]byte
のようなポインタ型として扱われるようになります。- これにより、エスケープ解析は、このスライスヘッダがヒープにエスケープするかどうかを正しく判断できるようになります。たとえスライスの要素型(
byte
)がポインタを含まなくても、スライスヘッダ自体は基底配列へのポインタを含んでいるため、この修正は不可欠です。
src/cmd/gc/order.c
の変更
orderexpr
関数は、式の順序付けと一時変数の割り当てを行う部分です。
変更された行: n->alloc = ordertemp(n->type->type, order, 0);
- 以前は
n->alloc = ordertemp(n->type, order, 0);
でした。ここでn->type
は可変引数全体の型(例:[]byte
)を指していました。 - 変更後は
n->type->type
となっています。これは、スライス型(n->type
)の要素型(byte
)を指します。 ordertemp
は一時変数を割り当てるための関数です。この変更は、可変引数の一時的なストレージを割り当てる際に、スライスヘッダではなく、その基底となる要素の型に基づいて割り当てを行うように修正しています。これは、エスケープ解析がスライスヘッダと基底配列の両方を正しく考慮するための、より正確な型情報を提供します。
test/escape2.go
の変更
このファイルには、エスケープ解析の動作を検証するためのテストケースが含まれています。
追加されたテストケース: foo150
とbar150
// issue 7934: missed ... if element type had no pointers
var save150 []byte
func foo150(x ...byte) { // ERROR "leaking param: x"
save150 = x
}
func bar150() {
foo150(1, 2, 3) // ERROR "[.]... argument escapes to heap"
}
foo150
関数は...byte
型の引数x
を受け取ります。x
をグローバル変数save150
に代入することで、x
(実際には[]byte
スライス)が関数のスコープを超えて生存する必要があるため、ヒープにエスケープすべきであることを示しています。bar150
関数はfoo150
を呼び出し、具体的なbyte
値を渡しています。// ERROR
コメントは、コンパイラがこの行で特定のエラーメッセージ(この場合はエスケープ解析に関する警告)を出すことを期待していることを示します。- このテストケースは、修正前はエスケープが検出されず、修正後には期待通りに「
leaking param: x
」と「... argument escapes to heap
」という警告が出力されることを確認します。これにより、...byte
のようなポインタを含まない要素型を持つ可変引数のエスケープ解析が正しく機能するようになったことが検証されます。
関連リンク
- Go Issue #7934: https://github.com/golang/go/issues/7934
- Go Code Review: https://golang.org/cl/100310043
参考にした情報源リンク
- Go言語の公式ドキュメント (エスケープ解析、可変引数に関する一般的な情報)
- Goコンパイラのソースコード (
src/cmd/gc/esc.c
,src/cmd/gc/order.c
) - Go言語のIssueトラッカー (Issue #7934)
- Go言語のCode Reviewシステム (CL 100310043)
- エスケープ解析に関する一般的なコンピュータサイエンスの概念
- Go言語におけるスライスの内部表現に関する情報```markdown
[インデックス 19305] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)におけるエスケープ解析のバグ修正に関するものです。特に、ポインタを含まない要素型を持つ可変引数(...
)が正しくエスケープ解析されない問題に対処しています。
コミット
cmd/gc: fix ... escape analysis bug
If the ... element type contained no pointers,
then the escape analysis did not track the ... itself.
This manifested in an escaping ...byte being treated
as non-escaping.
Fixes #7934.
LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/100310043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c99dce2b058b2260f05d694c1eaf0bbf16e79d27
元コミット内容
commit c99dce2b058b2260f05d694c1eaf0bbf16e79d27
Author: Russ Cox <rsc@golang.org>
Date: Fri May 9 15:40:45 2014 -0400
cmd/gc: fix ... escape analysis bug
If the ... element type contained no pointers,
then the escape analysis did not track the ... itself.
This manifested in an escaping ...byte being treated
as non-escaping.
Fixes #7934.
LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/100310043
---
src/cmd/gc/esc.c | 2 ++\n src/cmd/gc/order.c | 2 +-\n test/escape2.go | 28 ++\n 3 files changed, 23 insertions(+), 9 deletions(-)\n
変更の背景
Goコンパイラのエスケープ解析において、可変引数(...
)の要素型がポインタを含まない場合(例: ...byte
)、その可変引数自体が正しく追跡されないというバグが存在しました。この結果、本来ヒープにエスケープすべき...byte
型の値が、誤ってスタックに割り当てられると判断され、ガベージコレクションの対象外となる可能性がありました。これは、プログラムの誤動作やメモリリークに繋がる重大な問題です。
このコミットは、GitHub Issue #7934で報告されたこの問題を解決するために作成されました。エスケープ解析は、変数がプログラムのどの部分でアクセスされるかを分析し、その変数をスタックに割り当てるかヒープに割り当てるかを決定するコンパイラの重要な最適化機能です。このバグは、特に可変引数とポインタを含まない型との組み合わせで、この最適化が正しく機能しないことを示していました。
前提知識の解説
1. エスケープ解析 (Escape Analysis)
エスケープ解析は、コンパイラが行う最適化の一つで、変数がプログラムの実行中にどこに割り当てられるべきかを決定します。
- スタック割り当て (Stack Allocation): 関数内で宣言された変数が、その関数が終了するまでしか生存しない場合、通常はスタックに割り当てられます。スタックは高速で、ガベージコレクションのオーバーヘッドがありません。
- ヒープ割り当て (Heap Allocation): 変数が関数のスコープを超えて生存する必要がある場合(例: グローバル変数、他の関数に渡されるポインタ、クロージャによって参照される変数など)、ヒープに割り当てられます。ヒープはガベージコレクタによって管理され、スタックよりもアクセスが遅く、GCのオーバーヘッドが発生します。
エスケープ解析の目的は、可能な限り多くの変数をスタックに割り当てることで、プログラムのパフォーマンスを向上させ、ガベージコレクションの負担を軽減することです。
2. 可変引数 (Variadic Functions)
Go言語では、関数が不定数の引数を受け取ることができる「可変引数」という機能があります。これは、関数の最後のパラメータの型の前に...
を付けることで定義されます。
例: func sum(nums ...int)
この関数が呼び出されると、nums
は実際には[]int
型のスライスとして関数内で扱われます。例えば、sum(1, 2, 3)
と呼び出すと、関数内ではnums
が[]int{1, 2, 3}
として扱われます。
3. Goコンパイラの構造 (cmd/gc
)
cmd/gc
は、Go言語の公式コンパイラです。ソースコードを解析し、中間表現を生成し、最適化を行い、最終的に実行可能なバイナリを生成します。エスケープ解析は、このコンパイルプロセスの最適化フェーズの一部として実行されます。
4. Node
とType
Goコンパイラの内部では、プログラムの構造は抽象構文木(AST)として表現され、そのノードはNode
構造体で表されます。各ノードは、そのノードが表す式の型情報(Type
)を持っています。
typ(TARRAY)
: 配列型を表すType
を生成します。ptrto(Type)
: 指定されたType
へのポインタ型を表すType
を生成します。
技術的詳細
このバグは、可変引数(...
)がスライスとして扱われる際に、そのスライスの要素型がポインタを含まない場合に発生しました。具体的には、...byte
のような場合です。
エスケープ解析は、変数がポインタであるかどうか、またはポインタを含む構造体であるかどうかを追跡することで、その変数がヒープにエスケープするかどうかを判断します。しかし、...byte
のように要素型がbyte
(ポインタではない)である場合、コンパイラのエスケープ解析ロジックが、可変引数自体(つまり、[]byte
スライスヘッダ)がポインタとして扱われるべきであるという事実を見落としていました。
可変引数は、内部的にはスライスとして実装されます。このスライスヘッダは、基底配列へのポインタ、長さ、容量を含みます。たとえ要素型がポインタを含まなくても、このスライスヘッダ自体はポインタを含んでいるため、スライスが関数のスコープを超えて使用される場合(例: グローバル変数に代入される場合)、そのスライスヘッダはヒープにエスケープする必要があります。
このコミットの修正は、src/cmd/gc/esc.c
において、可変引数を表すNode
の型を明示的にポインタ型に変換することで、エスケープ解析がそのNode
を正しく追跡するように変更しました。これにより、...byte
のような可変引数も、そのスライスヘッダがヒープにエスケープする必要がある場合に正しく検出されるようになります。
また、src/cmd/gc/order.c
の変更は、可変引数の一時的な割り当てに関するものです。ordertemp
関数は一時変数を割り当てるために使用されますが、以前は可変引数全体の型(n->type
)を渡していました。しかし、可変引数はスライスであり、その実体は要素の配列です。この修正では、n->type->type
(つまり、スライスの要素型)を渡すことで、一時的な割り当てがより正確に要素の型に基づいて行われるようにしています。これは、エスケープ解析がスライスヘッダと基底配列の両方を正しく考慮するために必要な調整と考えられます。
test/escape2.go
の変更は、このバグを再現し、修正が正しく機能することを確認するための新しいテストケースを追加しています。foo150
関数は...byte
を受け取り、それをグローバル変数save150
に代入することで、x
がヒープにエスケープすることを意図しています。修正前は、このテストはエスケープを検出できませんでしたが、修正後は正しく検出されるようになります。
コアとなるコードの変更箇所
src/cmd/gc/esc.c
--- a/src/cmd/gc/esc.c
+++ b/src/cmd/gc/esc.c
@@ -903,6 +903,7 @@ esccall(EscState *e, Node *n, Node *up)
src->type = typ(TARRAY);
src->type->type = lr->n->type->type;
src->type->bound = count(ll);
+ src->type = ptrto(src->type); // make pointer so it will be tracked
src->escloopdepth = e->loopdepth;
src->lineno = n->lineno;
src->esc = EscNone; // until we find otherwise
@@ -960,6 +961,7 @@ esccall(EscState *e, Node *n, Node *up)
src->type = typ(TARRAY);
src->type->type = t->type->type;
src->type->bound = count(ll);
+ src->type = ptrto(src->type); // make pointer so it will be tracked
src->esc = EscNone; // until we find otherwise
e->noesc = list(e->noesc, src);
n->right = src;
src/cmd/gc/order.c
--- a/src/cmd/gc/order.c
+++ b/src/cmd/gc/order.c
@@ -1012,7 +1012,7 @@ orderexpr(Node **np, Order *order)
// Allocate a temporary that will be cleaned up when this statement
// completes. We could be more aggressive and try to arrange for it
// to be cleaned up when the call completes.
- n->alloc = ordertemp(n->type, order, 0);
+ n->alloc = ordertemp(n->type->type, order, 0);
}
break;
test/escape2.go
--- a/test/escape2.go
+++ b/test/escape2.go
@@ -1399,3 +1399,15 @@ func foo149(l List) { // ERROR " l does not escape"
}
}
}
+
+// issue 7934: missed ... if element type had no pointers
+
+var save150 []byte
+
+func foo150(x ...byte) { // ERROR "leaking param: x"
+ save150 = x
+}
+
+func bar150() {
+ foo150(1, 2, 3) // ERROR "[.]... argument escapes to heap"
+}
コアとなるコードの解説
src/cmd/gc/esc.c
の変更
esccall
関数は、関数呼び出しのエスケープ解析を行う部分です。可変引数(...
)が処理される際に、その引数を表すsrc
ノードの型が設定されます。
追加された行: src->type = ptrto(src->type); // make pointer so it will be tracked
この変更は、可変引数(内部的にはスライス)の型を、そのスライス型へのポインタ型に変換しています。
- 以前は、
src->type
は[]byte
のようなスライス型そのものでした。 - しかし、エスケープ解析のロジックは、ポインタ型の変数を追跡するように設計されています。
ptrto(src->type)
とすることで、src
ノードが*[]byte
のようなポインタ型として扱われるようになります。- これにより、エスケープ解析は、このスライスヘッダがヒープにエスケープするかどうかを正しく判断できるようになります。たとえスライスの要素型(
byte
)がポインタを含まなくても、スライスヘッダ自体は基底配列へのポインタを含んでいるため、この修正は不可欠です。
src/cmd/gc/order.c
の変更
orderexpr
関数は、式の順序付けと一時変数の割り当てを行う部分です。
変更された行: n->alloc = ordertemp(n->type->type, order, 0);
- 以前は
n->alloc = ordertemp(n->type, order, 0);
でした。ここでn->type
は可変引数全体の型(例:[]byte
)を指していました。 - 変更後は
n->type->type
となっています。これは、スライス型(n->type
)の要素型(byte
)を指します。 ordertemp
は一時変数を割り当てるための関数です。この変更は、可変引数の一時的なストレージを割り当てる際に、スライスヘッダではなく、その基底となる要素の型に基づいて割り当てを行うように修正しています。これは、エスケープ解析がスライスヘッダと基底配列の両方を正しく考慮するための、より正確な型情報を提供します。
test/escape2.go
の変更
このファイルには、エスケープ解析の動作を検証するためのテストケースが含まれています。
追加されたテストケース: foo150
とbar150
// issue 7934: missed ... if element type had no pointers
var save150 []byte
func foo150(x ...byte) { // ERROR "leaking param: x"
save150 = x
}
func bar150() {
foo150(1, 2, 3) // ERROR "[.]... argument escapes to heap"
}
foo150
関数は...byte
型の引数x
を受け取ります。x
をグローバル変数save150
に代入することで、x
(実際には[]byte
スライス)が関数のスコープを超えて生存する必要があるため、ヒープにエスケープすべきであることを示しています。bar150
関数はfoo150
を呼び出し、具体的なbyte
値を渡しています。// ERROR
コメントは、コンパイラがこの行で特定のエラーメッセージ(この場合はエスケープ解析に関する警告)を出すことを期待していることを示します。- このテストケースは、修正前はエスケープが検出されず、修正後には期待通りに「
leaking param: x
」と「... argument escapes to heap
」という警告が出力されることを確認します。これにより、...byte
のようなポインタを含まない要素型を持つ可変引数のエスケープ解析が正しく機能するようになったことが検証されます。
関連リンク
- Go Issue #7934: https://github.com/golang/go/issues/7934
- Go Code Review: https://golang.org/cl/100310043
参考にした情報源リンク
- Go言語の公式ドキュメント (エスケープ解析、可変引数に関する一般的な情報)
- Goコンパイラのソースコード (
src/cmd/gc/esc.c
,src/cmd/gc/order.c
) - Go言語のIssueトラッカー (Issue #7934)
- Go言語のCode Reviewシステム (CL 100310043)
- エスケープ解析に関する一般的なコンピュータサイエンスの概念
- Go言語におけるスライスの内部表現に関する情報