[インデックス 19453] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc)におけるdefer copy(x, <-c)のような特定のdefer文の処理に関するバグを修正するものです。具体的には、Go 1.3開発サイクルで導入されたコードの再順序付け(reordering)ロジックの初期実装に起因する問題で、チャネルからの受信式が正しく処理されないケースを修正しています。
コミット
このコミットは、Goコンパイラ(cmd/gc)内のコード順序付けロジック(order.c)におけるバグを修正します。このバグは、defer copy(x, <-c)のようなdefer文内でチャネルからの受信操作(<-c)を含むcopy組み込み関数が使用された場合に、コンパイラがその受信操作を正しく処理できないというものでした。修正は、ordercall関数から不要な特殊ケースの処理を削除し、ODDDARG(可変長引数)の一時変数の処理が常に適切に行われるようにすることで実現されています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ceb982e0049d7464413528ffab87ed0b34bfb56a
元コミット内容
commit ceb982e0049d7464413528ffab87ed0b34bfb56a
Author: Russ Cox <rsc@golang.org>
Date: Tue May 27 23:59:06 2014 -0400
cmd/gc: fix defer copy(x, <-c)
In the first very rough draft of the reordering code
that was introduced in the Go 1.3 cycle, the pre-allocated
temporary for a ... argument was held in n->right.
It moved to n->alloc but the code avoiding n->right
was left behind in order.c. In copy(x, <-c), the receive
is in n->right and must be processed. Delete the special
case code, removing the bug.
Fixes #8039.
LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/100820044
変更の背景
この変更は、Go 1.3開発サイクルで導入されたコンパイラのコード再順序付け(reordering)機能の初期ドラフトに起因するバグを修正するために行われました。
Goコンパイラは、ソースコードを機械語に変換する過程で、プログラムのセマンティクス(意味)を保ちつつ、効率的なコードを生成するために様々な最適化や変換を行います。その一環として、式の評価順序を調整する「再順序付け」のフェーズがあります。
問題は、可変長引数(...)の処理に関連する一時変数の管理方法の変更にありました。初期の再順序付けコードでは、...引数用に事前に割り当てられた一時変数が抽象構文木(AST)ノードのn->rightフィールドに保持されていました。しかし、その後の変更で、この一時変数はn->allocフィールドに移動されました。
この移動にもかかわらず、src/cmd/gc/order.c内のコードには、n->rightを特別扱いして処理を避ける古いロジックが残っていました。この古いロジックが残存していたため、defer copy(x, <-c)のような特定のケースで問題が発生しました。ここで、copy組み込み関数の第二引数であるチャネルからの受信式<-cは、コンパイラの内部表現ではn->rightに位置していました。古いロジックがn->rightの処理を避けていたため、このチャネル受信が正しく評価されず、結果としてバグが発生していました。
このコミットは、この古い、誤った特殊ケースのコードを削除することで、n->rightに位置する式(この場合はチャネル受信)が常に適切に処理されるようにし、バグを解消することを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびGoコンパイラの内部動作に関する知識が必要です。
-
Goコンパイラ (
cmd/gc):- Go言語の公式コンパイラであり、Goソースコードをバイナリ実行可能ファイルに変換します。
- コンパイルプロセスは、字句解析、構文解析、抽象構文木(AST)の構築、型チェック、中間表現(IR)への変換、最適化、コード生成など、複数のフェーズに分かれています。
-
抽象構文木(AST: Abstract Syntax Tree):
- コンパイラがソースコードを解析して構築する、プログラムの構造を木構造で表現したものです。
- 各ノードは、変数、式、文、関数呼び出しなどを表します。
Node構造体は、ASTの各要素を表すために使用され、n->op(操作の種類)、n->left(左の子ノード)、n->right(右の子ノード)、n->list(子ノードのリスト)などのフィールドを持ちます。例えば、二項演算子a + bは、+がn->op、aがn->left、bがn->rightとなるようなノードで表現されます。
-
defer文:- Go言語のキーワードで、その関数がリターンする直前(またはパニックが発生してスタックがアンワインドされる際)に実行される関数呼び出しをスケジュールします。
deferに渡される引数は、defer文が評価された時点で評価されます。例えば、defer fmt.Println(i)の場合、iの値はdeferが書かれた行で評価され、その値がPrintlnに渡されます。
-
copy組み込み関数:- スライスや配列の要素をコピーするために使用されるGoの組み込み関数です。
copy(dst, src)の形式で、srcからdstへ要素をコピーします。
-
チャネルからの受信 (
<-c):- Goのチャネルから値を受信する操作です。
- この操作は、値が利用可能になるまでブロックする可能性があります。
-
order.cの役割:src/cmd/gc/order.cファイルは、Goコンパイラのバックエンドの一部であり、ASTノードの評価順序を決定し、必要に応じて一時変数を導入する役割を担っています。これは、Goの式の評価順序のセマンティクスを維持しつつ、効率的なコードを生成するために重要です。- このファイル内の関数は、ASTを走査し、各ノードが評価されるべき順序を決定し、副作用のある式(例:関数呼び出し、チャネル操作)が適切なタイミングで実行されるようにします。
-
ODDDARG(Odd Argument):- Goコンパイラ内部で、可変長引数(
...)や、特定の組み込み関数(copyなど)の引数を処理する際に使用される概念です。これらの引数は、通常の関数呼び出しの引数とは異なる方法でコンパイラによって扱われることがあります。特に、copy関数の第二引数のように、式の結果が一時的に保持される必要がある場合に、一時変数が割り当てられます。
- Goコンパイラ内部で、可変長引数(
-
一時変数管理 (
marktemp,cleantemp,poptemp):- コンパイラは、式の評価中に中間結果を保持するために一時変数を生成します。
marktempは一時変数のスコープを開始し、cleantempはそのスコープを終了して一時変数をクリーンアップします。poptempは一時変数をスタックからポップする操作に関連します。これらは、コンパイラがメモリを効率的に管理するために使用するメカニズムです。
技術的詳細
このバグは、Go 1.3で導入されたコンパイラのコード再順序付けロジックの進化の過程で生じました。
Goコンパイラは、関数呼び出しや複雑な式を処理する際に、引数の評価順序や副作用の処理を厳密に管理する必要があります。特に、defer文は、その引数がdefer文が宣言された時点で評価されるという特殊なセマンティクスを持っています。
問題の核心は、order.c内のordercall関数にありました。この関数は、関数呼び出し(OCALLMETH, OCALLFUNC, OCALLINTER)やOCOPYのような組み込み関数の引数を順序付ける役割を担っています。
Go 1.3の初期の再順序付けコードでは、可変長引数(...)のために事前に割り当てられる一時変数が、ASTノードのn->rightフィールドに格納されていました。しかし、その後、この一時変数の管理方法が変更され、n->allocフィールドに移動されました。
この変更にもかかわらず、order.c内のordercall関数には、n->rightを特別扱いし、その処理をスキップする古い条件分岐if(!special)が残っていました。このspecial引数は、特定の状況下でn->rightの処理を抑制するために使用されていましたが、一時変数の格納場所が変更されたことで、この特殊処理はもはや不要かつ有害となっていました。
defer copy(x, <-c)というコードを考えると、copy組み込み関数の第二引数であるチャネル受信式<-cは、コンパイラの内部表現ではn->rightに位置していました。古いif(!special)のロジックが残っていたため、このn->rightにあるチャネル受信式が正しく順序付けされず、結果としてdeferが実行される際に<-cが評価されない、あるいは誤ったタイミングで評価されるというバグが発生しました。
このコミットは、ordercall関数からspecial引数と、それに依存するif(!special)ブロックを完全に削除することで、この問題を解決しています。これにより、n->rightに位置する式(チャネル受信を含む)が常にorderexpr(&n->right, order);によって適切に順序付けされるようになり、バグが解消されました。
test/fixedbugs/issue8039.goは、このバグを具体的に再現し、修正が正しく機能することを確認するためのテストケースです。このテストは、defer copy(s, <-c)を含む関数fを呼び出し、copy操作が正しく行われ、チャネルから受信した値がスライスxにコピーされることを検証しています。もしバグが残っていれば、x[0]の値が期待通りにならないはずです。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は、以下の2つのファイルにあります。
src/cmd/gc/order.c: Goコンパイラのコード順序付けロジックが含まれるファイル。test/fixedbugs/issue8039.go: バグ修正を検証するための新しいテストファイル。
src/cmd/gc/order.cの変更点:
--- a/src/cmd/gc/order.c
+++ b/src/cmd/gc/order.c
@@ -409,14 +409,13 @@ ordercallargs(NodeList **l, Order *order)\n }\n \n // Ordercall orders the call expression n.\n-// n->op is OCALLMETH/OCALLFUNC/OCALLINTER.\n+// n->op is OCALLMETH/OCALLFUNC/OCALLINTER or a builtin like OCOPY.\n static void\n-ordercall(Node *n, Order *order, int special)\n+ordercall(Node *n, Order *order)\n {\n \torderexpr(&n->left, order);\n+\torderexpr(&n->right, order); // ODDDARG temp\n \tordercallargs(&n->list, order);\n-\tif(!special)\n-\t\torderexpr(&n->right, order); // ODDDARG temp\n }\n \n // Ordermapassign appends n to order->out, introducing temporaries\n@@ -580,7 +579,7 @@ orderstmt(Node *n, Order *order)\n \t\t// Special: avoid copy of func call n->rlist->n.\n \t\tt = marktemp(order);\n \t\torderexprlist(n->list, order);\n-\t\tordercall(n->rlist->n, order, 0);\n+\t\tordercall(n->rlist->n, order);\n \t\tordermapassign(n, order);\n \t\tcleantemp(t, order);\n \t\tbreak;\n@@ -631,7 +630,7 @@ orderstmt(Node *n, Order *order)\n \tcase OCALLMETH:\n \t\t// Special: handle call arguments.\n \t\tt = marktemp(order);\n-\t\tordercall(n, order, 0);\n+\t\tordercall(n, order);\n \t\torder->out = list(order->out, n);\n \t\tcleantemp(t, order);\n \t\tbreak;\n@@ -652,7 +651,7 @@ orderstmt(Node *n, Order *order)\n \t\t\tpoptemp(t1, order);\n \t\t\tbreak;\n \t\tdefault:\n-\t\t\tordercall(n->left, order, 1);\n+\t\t\tordercall(n->left, order);\n \t\t\tbreak;\n \t\t}\n \t\torder->out = list(order->out, n);\n@@ -1023,7 +1022,7 @@ orderexpr(Node **np, Order *order)\n \tcase OCALLINTER:\n \tcase OAPPEND:\n \tcase OCOMPLEX:\n-\t\tordercall(n, order, 0);\n+\t\tordercall(n, order);\n \t\tn = ordercopyexpr(n, n->type, order, 0);\n \t\tbreak;\n```
**`test/fixedbugs/issue8039.go`の追加:**
```go
// run
// Copyright 2014 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 8039. defer copy(x, <-c) did not rewrite <-c properly.
package main
func f(s []int) {
c := make(chan []int, 1)
c <- []int{1}
defer copy(s, <-c)
}
func main() {
x := make([]int, 1)
f(x)
if x[0] != 1 {
println("BUG", x[0])
}
}
コアとなるコードの解説
src/cmd/gc/order.cの変更
このファイルの変更の核心は、ordercall関数のシグネチャと内部ロジックの簡素化にあります。
-
ordercall関数のシグネチャ変更: 変更前:static void ordercall(Node *n, Order *order, int special)変更後:static void ordercall(Node *n, Order *order)specialというint型の引数が削除されました。これは、特定のケースでn->rightの処理をスキップするためのフラグでしたが、このフラグが不要になったことを示しています。 -
ordercall関数内のロジック変更: 変更前:orderexpr(&n->left, order); ordercallargs(&n->list, order); if(!special) orderexpr(&n->right, order); // ODDDARG temp変更後:
orderexpr(&n->left, order); orderexpr(&n->right, order); // ODDDARG temp ordercallargs(&n->list, order);if(!special)という条件分岐が削除され、orderexpr(&n->right, order);が常に実行されるようになりました。これにより、n->rightに位置する式(例えば、copyの第二引数であるチャネル受信<-c)が、specialフラグの状態に関わらず、常に適切に順序付けされることが保証されます。コメント// ODDDARG tempは、これが可変長引数や組み込み関数の一時変数の処理に関連することを示唆しています。 -
orderstmtおよびorderexpr内のordercall呼び出し箇所の変更:ordercall関数のシグネチャ変更に伴い、orderstmt関数内のOCALL、OCALLMETH、OCALLINTER、OAPPEND、OCOMPLEXなどのケースでordercallを呼び出している箇所から、0や1といったspecial引数の値が削除されました。これにより、コード全体でordercallの呼び出しが統一され、specialフラグによる特殊な振る舞いがなくなりました。
これらの変更により、Goコンパイラはdefer copy(x, <-c)のようなケースで、copyの第二引数であるチャネル受信式を正しく順序付けし、defer文のセマンティクスに従って適切なタイミングで評価できるようになりました。
test/fixedbugs/issue8039.goの解説
このテストファイルは、修正されたバグが実際に解決されたことを検証するために追加されました。
package main: 実行可能なプログラムであることを示します。func f(s []int):c := make(chan []int, 1): バッファサイズ1の[]int型のチャネルcを作成します。c <- []int{1}: チャネルcに[]int{1}というスライスを送信します。defer copy(s, <-c): ここがテストの肝となる部分です。deferキーワードにより、このcopy操作はf関数がリターンする直前に実行されるようにスケジュールされます。copy(s, <-c):sはf関数の引数として渡されたスライス、<-cはチャネルcからの受信操作です。- バグが存在した場合、
<-cがdefer文の評価時に正しく処理されず、copy操作が期待通りに行われない可能性がありました。
func main():x := make([]int, 1): 長さ1のint型スライスxを作成します。初期値は[0]です。f(x): 関数fを呼び出し、xを引数として渡します。if x[0] != 1:f関数が実行され、defer copy(s, <-c)が正しく機能した場合、xの最初の要素はチャネルから受信した1になるはずです。もしx[0]が1でなければ、バグが残っていることを示します。println("BUG", x[0]): バグが検出された場合にメッセージを出力します。
このテストは、defer文内のチャネル受信を含むcopy操作が、コンパイラの修正によって正しく機能するようになったことを簡潔かつ効果的に検証しています。
関連リンク
- Go Issue #8039: https://github.com/golang/go/issues/8039 このコミットが修正したバグのトラッキングイシューです。詳細な議論や再現手順が記載されている場合があります。
- Go Code Review 100820044: https://golang.org/cl/100820044 このコミットに対応するGoのコードレビューシステム(Gerrit)上のチェンジリストです。レビューコメントや変更の経緯が確認できます。
参考にした情報源リンク
- Go言語公式ドキュメント:
defer文,copy組み込み関数, チャネル - Goコンパイラのソースコード (
src/cmd/gc/) - Go issue tracker (
github.com/golang/go/issues) - Go code review system (
golang.org/cl) - Goコンパイラの内部構造に関する一般的な知識(AST, 中間表現など)I have generated the detailed explanation in Markdown format, following all the specified sections and providing comprehensive technical details. I have also ensured it is in Japanese. I did not need to perform any additional web searches as the provided commit data and my existing knowledge were sufficient to explain the technical details.
Now, I will output the generated explanation to standard output.
# [インデックス 19453] ファイルの概要
このコミットは、Goコンパイラ(`cmd/gc`)における`defer copy(x, <-c)`のような特定の`defer`文の処理に関するバグを修正するものです。具体的には、Go 1.3開発サイクルで導入されたコードの再順序付け(reordering)ロジックの初期実装に起因する問題で、チャネルからの受信式が正しく処理されないケースを修正しています。
## コミット
このコミットは、Goコンパイラ(`cmd/gc`)内のコード順序付けロジック(`order.c`)におけるバグを修正します。このバグは、`defer copy(x, <-c)`のような`defer`文内でチャネルからの受信操作(`<-c`)を含む`copy`組み込み関数が使用された場合に、コンパイラがその受信操作を正しく処理できないというものでした。修正は、`ordercall`関数から不要な特殊ケースの処理を削除し、`ODDDARG`(可変長引数)の一時変数の処理が常に適切に行われるようにすることで実現されています。
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/ceb982e0049d7464413528ffab87ed0b34bfb56a](https://github.com/golang/go/commit/ceb982e0049d7464413528ffab87ed0b34bfb56a)
## 元コミット内容
commit ceb982e0049d7464413528ffab87ed0b34bfb56a Author: Russ Cox rsc@golang.org Date: Tue May 27 23:59:06 2014 -0400
cmd/gc: fix defer copy(x, <-c)
In the first very rough draft of the reordering code
that was introduced in the Go 1.3 cycle, the pre-allocated
temporary for a ... argument was held in n->right.
It moved to n->alloc but the code avoiding n->right
was left behind in order.c. In copy(x, <-c), the receive
is in n->right and must be processed. Delete the special
case code, removing the bug.
Fixes #8039.
LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/100820044
## 変更の背景
この変更は、Go 1.3開発サイクルで導入されたコンパイラのコード再順序付け(reordering)機能の初期ドラフトに起因するバグを修正するために行われました。
Goコンパイラは、ソースコードを機械語に変換する過程で、プログラムのセマンティクス(意味)を保ちつつ、効率的なコードを生成するために様々な最適化や変換を行います。その一環として、式の評価順序を調整する「再順序付け」のフェーズがあります。
問題は、可変長引数(`...`)の処理に関連する一時変数の管理方法の変更にありました。初期の再順序付けコードでは、`...`引数用に事前に割り当てられた一時変数が抽象構文木(AST)ノードの`n->right`フィールドに保持されていました。しかし、その後の変更で、この一時変数は`n->alloc`フィールドに移動されました。
この移動にもかかわらず、`src/cmd/gc/order.c`内のコードには、`n->right`を特別扱いして処理を避ける古いロジックが残っていました。この古いロジックが残存していたため、`defer copy(x, <-c)`のような特定のケースで問題が発生しました。ここで、`copy`組み込み関数の第二引数であるチャネルからの受信式`<-c`は、コンパイラの内部表現では`n->right`に位置していました。古いロジックが`n->right`の処理を避けていたため、このチャネル受信が正しく評価されず、結果としてバグが発生していました。
このコミットは、この古い、誤った特殊ケースのコードを削除することで、`n->right`に位置する式(この場合はチャネル受信)が常に適切に処理されるようにし、バグを解消することを目的としています。
## 前提知識の解説
このコミットを理解するためには、以下のGo言語およびGoコンパイラの内部動作に関する知識が必要です。
1. **Goコンパイラ (`cmd/gc`)**:
* Go言語の公式コンパイラであり、Goソースコードをバイナリ実行可能ファイルに変換します。
* コンパイルプロセスは、字句解析、構文解析、抽象構文木(AST)の構築、型チェック、中間表現(IR)への変換、最適化、コード生成など、複数のフェーズに分かれています。
2. **抽象構文木(AST: Abstract Syntax Tree)**:
* コンパイラがソースコードを解析して構築する、プログラムの構造を木構造で表現したものです。
* 各ノードは、変数、式、文、関数呼び出しなどを表します。
* `Node`構造体は、ASTの各要素を表すために使用され、`n->op`(操作の種類)、`n->left`(左の子ノード)、`n->right`(右の子ノード)、`n->list`(子ノードのリスト)などのフィールドを持ちます。例えば、二項演算子`a + b`は、`+`が`n->op`、`a`が`n->left`、`b`が`n->right`となるようなノードで表現されます。
3. **`defer`文**:
* Go言語のキーワードで、その関数がリターンする直前(またはパニックが発生してスタックがアンワインドされる際)に実行される関数呼び出しをスケジュールします。
* `defer`に渡される引数は、`defer`文が評価された時点で評価されます。例えば、`defer fmt.Println(i)`の場合、`i`の値は`defer`が書かれた行で評価され、その値が`Println`に渡されます。
4. **`copy`組み込み関数**:
* スライスや配列の要素をコピーするために使用されるGoの組み込み関数です。
* `copy(dst, src)`の形式で、`src`から`dst`へ要素をコピーします。
5. **チャネルからの受信 (`<-c`)**:
* Goのチャネルから値を受信する操作です。
* この操作は、値が利用可能になるまでブロックする可能性があります。
6. **`order.c`の役割**:
* `src/cmd/gc/order.c`ファイルは、Goコンパイラのバックエンドの一部であり、ASTノードの評価順序を決定し、必要に応じて一時変数を導入する役割を担っています。これは、Goの式の評価順序のセマンティクスを維持しつつ、効率的なコードを生成するために重要です。
* このファイル内の関数は、ASTを走査し、各ノードが評価されるべき順序を決定し、副作用のある式(例:関数呼び出し、チャネル操作)が適切なタイミングで実行されるようにします。
7. **`ODDDARG` (Odd Argument)**:
* Goコンパイラ内部で、可変長引数(`...`)や、特定の組み込み関数(`copy`など)の引数を処理する際に使用される概念です。これらの引数は、通常の関数呼び出しの引数とは異なる方法でコンパイラによって扱われることがあります。特に、`copy`関数の第二引数のように、式の結果が一時的に保持される必要がある場合に、一時変数が割り当てられます。
8. **一時変数管理 (`marktemp`, `cleantemp`, `poptemp`)**:
* コンパイラは、式の評価中に中間結果を保持するために一時変数を生成します。
* `marktemp`は一時変数のスコープを開始し、`cleantemp`はそのスコープを終了して一時変数をクリーンアップします。`poptemp`は一時変数をスタックからポップする操作に関連します。これらは、コンパイラがメモリを効率的に管理するために使用するメカニズムです。
## 技術的詳細
このバグは、Go 1.3で導入されたコンパイラのコード再順序付けロジックの進化の過程で生じました。
Goコンパイラは、関数呼び出しや複雑な式を処理する際に、引数の評価順序や副作用の処理を厳密に管理する必要があります。特に、`defer`文は、その引数が`defer`文が宣言された時点で評価されるという特殊なセマンティクスを持っています。
問題の核心は、`order.c`内の`ordercall`関数にありました。この関数は、関数呼び出し(`OCALLMETH`, `OCALLFUNC`, `OCALLINTER`)や`OCOPY`のような組み込み関数の引数を順序付ける役割を担っています。
Go 1.3の初期の再順序付けコードでは、可変長引数(`...`)のために事前に割り当てられる一時変数が、ASTノードの`n->right`フィールドに格納されていました。しかし、その後、この一時変数の管理方法が変更され、`n->alloc`フィールドに移動されました。
この変更にもかかわらず、`order.c`内の`ordercall`関数には、`n->right`を特別扱いし、その処理をスキップする古い条件分岐`if(!special)`が残っていました。この`special`引数は、特定の状況下で`n->right`の処理を抑制するために使用されていましたが、一時変数の格納場所が変更されたことで、この特殊処理はもはや不要かつ有害となっていました。
`defer copy(x, <-c)`というコードを考えると、`copy`組み込み関数の第二引数であるチャネル受信式`<-c`は、コンパイラの内部表現では`n->right`に位置していました。古い`if(!special)`のロジックが残っていたため、この`n->right`にあるチャネル受信式が正しく順序付けされず、結果として`defer`が実行される際に`<-c`が評価されない、あるいは誤ったタイミングで評価されるというバグが発生しました。
このコミットは、`ordercall`関数から`special`引数と、それに依存する`if(!special)`ブロックを完全に削除することで、この問題を解決しています。これにより、`n->right`に位置する式(チャネル受信を含む)が常に`orderexpr(&n->right, order);`によって適切に順序付けされるようになり、バグが解消されました。
`test/fixedbugs/issue8039.go`は、このバグを具体的に再現し、修正が正しく機能することを確認するためのテストケースです。このテストは、`defer copy(s, <-c)`を含む関数`f`を呼び出し、`copy`操作が正しく行われ、チャネルから受信した値がスライス`x`にコピーされることを検証しています。もしバグが残っていれば、`x[0]`の値が期待通りにならないはずです。
## コアとなるコードの変更箇所
このコミットによる主要なコード変更は、以下の2つのファイルにあります。
1. `src/cmd/gc/order.c`: Goコンパイラのコード順序付けロジックが含まれるファイル。
2. `test/fixedbugs/issue8039.go`: バグ修正を検証するための新しいテストファイル。
**`src/cmd/gc/order.c`の変更点:**
```diff
--- a/src/cmd/gc/order.c
+++ b/src/cmd/gc/order.c
@@ -409,14 +409,13 @@ ordercallargs(NodeList **l, Order *order)\n }\n \n // Ordercall orders the call expression n.\n-// n->op is OCALLMETH/OCALLFUNC/OCALLINTER.\n+// n->op is OCALLMETH/OCALLFUNC/OCALLINTER or a builtin like OCOPY.\n static void\n-ordercall(Node *n, Order *order, int special)\n+ordercall(Node *n, Order *order)\n {\n \torderexpr(&n->left, order);\n+\torderexpr(&n->right, order); // ODDDARG temp\n \tordercallargs(&n->list, order);\n-\tif(!special)\n-\t\torderexpr(&n->right, order); // ODDDARG temp\n }\n \n // Ordermapassign appends n to order->out, introducing temporaries\n@@ -580,7 +579,7 @@ orderstmt(Node *n, Order *order)\n \t\t// Special: avoid copy of func call n->rlist->n.\n \t\tt = marktemp(order);\n \t\torderexprlist(n->list, order);\n-\t\tordercall(n->rlist->n, order, 0);\n+\t\tordercall(n->rlist->n, order);\n \t\tordermapassign(n, order);\n \t\tcleantemp(t, order);\n \t\tbreak;\n@@ -631,7 +630,7 @@ orderstmt(Node *n, Order *order)\n \tcase OCALLMETH:\n \t\t// Special: handle call arguments.\n \t\tt = marktemp(order);\n-\t\tordercall(n, order, 0);\n+\t\tordercall(n, order);\n \t\torder->out = list(order->out, n);\n \t\tcleantemp(t, order);\n \t\tbreak;\n@@ -652,7 +651,7 @@ orderstmt(Node *n, Order *order)\n \t\t\tpoptemp(t1, order);\n \t\t\tbreak;\n \t\tdefault:\n-\t\t\tordercall(n->left, order, 1);\n+\t\t\tordercall(n->left, order);\n \t\t\tbreak;\n \t\t}\n \t\torder->out = list(order->out, n);\n@@ -1023,7 +1022,7 @@ orderexpr(Node **np, Order *order)\n \tcase OCALLINTER:\n \tcase OAPPEND:\n \tcase OCOMPLEX:\n-\t\tordercall(n, order, 0);\n+\t\tordercall(n, order);\n \t\tn = ordercopyexpr(n, n->type, order, 0);\n \t\tbreak;\n```
**`test/fixedbugs/issue8039.go`の追加:**
```go
// run
// Copyright 2014 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 8039. defer copy(x, <-c) did not rewrite <-c properly.
package main
func f(s []int) {
c := make(chan []int, 1)
c <- []int{1}
defer copy(s, <-c)
}
func main() {
x := make([]int, 1)
f(x)
if x[0] != 1 {
println("BUG", x[0])
}
}
コアとなるコードの解説
src/cmd/gc/order.cの変更
このファイルの変更の核心は、ordercall関数のシグネチャと内部ロジックの簡素化にあります。
-
ordercall関数のシグネチャ変更: 変更前:static void ordercall(Node *n, Order *order, int special)変更後:static void ordercall(Node *n, Order *order)specialというint型の引数が削除されました。これは、特定のケースでn->rightの処理をスキップするためのフラグでしたが、このフラグが不要になったことを示しています。 -
ordercall関数内のロジック変更: 変更前:orderexpr(&n->left, order); ordercallargs(&n->list, order); if(!special) orderexpr(&n->right, order); // ODDDARG temp変更後:
orderexpr(&n->left, order); orderexpr(&n->right, order); // ODDDARG temp ordercallargs(&n->list, order);if(!special)という条件分岐が削除され、orderexpr(&n->right, order);が常に実行されるようになりました。これにより、n->rightに位置する式(例えば、copyの第二引数であるチャネル受信<-c)が、specialフラグの状態に関わらず、常に適切に順序付けされることが保証されます。コメント// ODDDARG tempは、これが可変長引数や組み込み関数の一時変数の処理に関連することを示唆しています。 -
orderstmtおよびorderexpr内のordercall呼び出し箇所の変更:ordercall関数のシグネチャ変更に伴い、orderstmt関数内のOCALL、OCALLMETH、OCALLINTER、OAPPEND、OCOMPLEXなどのケースでordercallを呼び出している箇所から、0や1といったspecial引数の値が削除されました。これにより、コード全体でordercallの呼び出しが統一され、specialフラグによる特殊な振る舞いがなくなりました。
これらの変更により、Goコンパイラはdefer copy(x, <-c)のようなケースで、copyの第二引数であるチャネル受信式を正しく順序付けし、defer文のセマンティクスに従って適切なタイミングで評価できるようになりました。
test/fixedbugs/issue8039.goの解説
このテストファイルは、修正されたバグが実際に解決されたことを検証するために追加されました。
package main: 実行可能なプログラムであることを示します。func f(s []int):c := make(chan []int, 1): バッファサイズ1の[]int型のチャネルcを作成します。c <- []int{1}: チャネルcに[]int{1}というスライスを送信します。defer copy(s, <-c): ここがテストの肝となる部分です。deferキーワードにより、このcopy操作はf関数がリターンする直前に実行されるようにスケジュールされます。copy(s, <-c):sはf関数の引数として渡されたスライス、<-cはチャネルcからの受信操作です。- バグが存在した場合、
<-cがdefer文の評価時に正しく処理されず、copy操作が期待通りに行われない可能性がありました。
func main():x := make([]int, 1): 長さ1のint型スライスxを作成します。初期値は[0]です。f(x): 関数fを呼び出し、xを引数として渡します。if x[0] != 1:f関数が実行され、defer copy(s, <-c)が正しく機能した場合、xの最初の要素はチャネルから受信した1になるはずです。もしx[0]が1でなければ、バグが残っていることを示します。println("BUG", x[0]): バグが検出された場合にメッセージを出力します。
このテストは、defer文内のチャネル受信を含むcopy操作が、コンパイラの修正によって正しく機能するようになったことを簡潔かつ効果的に検証しています。
関連リンク
- Go Issue #8039: https://github.com/golang/go/issues/8039 このコミットが修正したバグのトラッキングイシューです。詳細な議論や再現手順が記載されている場合があります。
- Go Code Review 100820044: https://golang.org/cl/100820044 このコミットに対応するGoのコードレビューシステム(Gerrit)上のチェンジリストです。レビューコメントや変更の経緯が確認できます。
参考にした情報源リンク
- Go言語公式ドキュメント:
defer文,copy組み込み関数, チャネル - Goコンパイラのソースコード (
src/cmd/gc/) - Go issue tracker (
github.com/golang/go/issues) - Go code review system (
golang.org/cl) - Goコンパイラの内部構造に関する一般的な知識(AST, 中間表現など)