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

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

このコミットは、Goコンパイラ(cmd/gc)におけるバグ修正と、そのバグを再現・検証するためのテストケースの追加を含んでいます。具体的には、チャネルからの受信操作(<-)がネストされている場合に、コンパイラが一時変数を適切に処理できない問題が修正されました。

変更されたファイルは以下の通りです。

  • src/cmd/gc/order.c: Goコンパイラのソースコード。式の評価順序を決定し、一時変数を導入するorderexpr関数が修正されました。
  • test/fixedbugs/issue8011.go: issue8011として報告されたバグを再現し、修正を検証するための新しいテストファイルです。

コミット

commit a663e0a0381c22d3b1ef58b411df5cfbf56c2930
Author: Russ Cox <rsc@golang.org>
Date:   Mon May 19 15:08:04 2014 -0400

    cmd/gc: fix <-<-expr
    
    The temporary-introducing pass was not recursing
    into the argumnt of a receive operation.
    
    Fixes #8011.
    
    LGTM=r
    R=golang-codereviews, r
    CC=golang-codereviews, iant, khr
    https://golang.org/cl/91540043

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

https://github.com/golang/go/commit/a663e0a0381c22d3b1ef58b411df5cfbf56c2930

元コミット内容

cmd/gc: fix <-<-expr

The temporary-introducing pass was not recursing
into the argumnt of a receive operation.

Fixes #8011.

LGTM=r
R=golang-codereviews, r
CC=golang-codereviews, iant, khr
https://golang.org/cl/91540043

変更の背景

このコミットは、Goコンパイラ(cmd/gc)が、ネストされたチャネル受信操作(例: x := <-<-c)を正しく処理できないというバグを修正するために行われました。

具体的には、コンパイラの「一時変数導入パス (temporary-introducing pass)」と呼ばれる段階で問題が発生していました。このパスは、複雑な式の中間結果を格納するための一時変数を導入する役割を担っています。しかし、チャネル受信操作(ORECV)の引数(つまり、受信するチャネル自体)に対して、このパスが再帰的に適用されていませんでした。

その結果、<-<-c のような式では、内側の受信操作 <-c の結果が、外側の受信操作の引数として渡される際に、一時変数として適切に処理されず、コンパイルされたコードが期待通りの動作をしないという問題が発生していました。このバグはGoのIssue #8011として報告されていました。

前提知識の解説

Go言語のチャネルと受信操作 (<-)

Go言語のチャネルは、ゴルーチン間で値を送受信するための強力な同期プリミティブです。チャネルからの値の受信は <-ch のように記述されます。この操作は、チャネル ch から値が利用可能になるまでゴルーチンをブロックする可能性があります。

Goコンパイラ (cmd/gc) の役割

cmd/gc は、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する主要なツールであり、構文解析、型チェック、最適化、コード生成など、複数のフェーズを経て処理を行います。

コンパイラの最適化パスと「一時変数導入パス」

コンパイラは、ソースコードをより効率的な機械語に変換するために、様々な「パス」と呼ばれる処理段階を経ます。これらのパスは、コードの最適化や、後のコード生成フェーズで扱いやすい形式への変換を行います。

「一時変数導入パス (temporary-introducing pass)」は、コンパイラの重要なフェーズの一つです。これは、複雑な式を評価する際に、中間結果を格納するための一時変数を導入する役割を担います。例えば、a + b * c のような式では、b * c の結果を一時変数に格納し、その一時変数と a を加算するといった処理が行われます。これにより、コンパイラは式の評価順序を明確にし、レジスタ割り当てやコード生成を容易にします。

orderexpr 関数の役割

src/cmd/gc/order.c に存在する orderexpr 関数は、Goコンパイラの「ordering」フェーズにおける中心的な関数です。この関数は、抽象構文木(AST)のノードを走査し、式の評価順序を決定します。また、必要に応じて一時変数を導入し、複雑な式をより単純な操作のシーケンスに分解します。これにより、後続のコード生成フェーズが効率的に行えるようになります。

技術的詳細

このバグは、orderexpr 関数が ORECV(チャネル受信操作)ノードを処理する際の不備に起因していました。

orderexpr 関数は、様々な種類のASTノード(変数、定数、演算子、関数呼び出しなど)を処理するための switch ステートメントを持っています。ORECV のケースでは、受信操作自体は処理されていましたが、その「引数」、つまり受信を行うチャネルを表す式(n->left)に対して、orderexpr が再帰的に呼び出されていませんでした。

ネストされた受信操作 x := <-<-c を考えます。

  1. 外側の受信操作 <- (<-c)orderexpr に渡されます。
  2. orderexprORECV ケースに入ります。
  3. この修正前は、n->left(この場合は内側の受信操作 <-c)に対して orderexpr が再帰的に呼び出されませんでした。
  4. その結果、内側の受信操作 <-c が生成する一時的なチャネル値が、外側の受信操作のコンテキストで適切に「順序付け」されず、一時変数として扱われない可能性がありました。これにより、コンパイラが不正なコードを生成し、ランタイムエラーや予期せぬ動作を引き起こす原因となっていました。

修正は、ORECV のケースに orderexpr(&n->left, order); を追加することで、この問題を解決しました。これにより、受信操作の引数であるチャネル式も、一時変数導入パスによって適切に処理されるようになり、ネストされた受信操作が正しくコンパイルされるようになりました。

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

src/cmd/gc/order.c の変更

--- a/src/cmd/gc/order.c
+++ b/src/cmd/gc/order.c
@@ -1053,6 +1053,7 @@ orderexpr(Node **np, Order *order)
 		break;
 
 	case ORECV:
+		orderexpr(&n->left, order);
 		n = ordercopyexpr(n, n->type, order, 1);
 		break;
 	}

この変更は、orderexpr 関数内の ORECV(チャネル受信)を処理する case に、orderexpr(&n->left, order); という行を追加しています。

test/fixedbugs/issue8011.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.

package main

func main() {
	c := make(chan chan int, 1)
	c1 := make(chan int, 1)
	c1 <- 42
	c <- c1
	x := <-<-c
	if x != 42 {
		println("BUG:", x, "!= 42")
	}
}

この新しいテストファイルは、ネストされたチャネル受信操作 x := <-<-c を使用して、バグを再現します。

  1. cchan chan int 型のチャネルです(チャネルのチャネル)。
  2. c1chan int 型のチャネルです。
  3. c142 を送信します。
  4. cc1 を送信します。
  5. x := <-<-c で、c から chan int を受信し、さらにその chan int から int を受信します。
  6. 最終的に x42 でない場合、「BUG:」メッセージを出力してテストが失敗することを示します。修正が適用されていれば、x42 になり、テストは成功します。

コアとなるコードの解説

src/cmd/gc/order.c の変更は非常に小さいですが、コンパイラの動作に大きな影響を与えます。

追加された行 orderexpr(&n->left, order); は、ORECV ノードの左の子(n->left)に対して、再度 orderexpr 関数を呼び出すことを意味します。ORECV ノードの左の子は、受信操作の対象となるチャネル式を表します。

この再帰的な呼び出しにより、ネストされた受信操作(例: <-<-c の内側の <-c)が、一時変数導入パスによって適切に処理されるようになります。具体的には、内側の受信操作の結果が一時変数として正しく「順序付け」され、外側の受信操作がその一時変数を引数として使用できるようになります。これにより、コンパイラはネストされた受信操作に対しても正しい機械語を生成できるようになり、Goプログラムの堅牢性が向上しました。

test/fixedbugs/issue8011.go は、この修正が正しく機能していることを検証するための重要なテストケースです。このような具体的なバグ再現テストは、コンパイラの品質保証において不可欠です。

関連リンク

参考にした情報源リンク

  • Go Issue #8011 の詳細
  • Goコンパイラのcmd/gcにおけるorderexpr関数の役割に関する一般的な情報(Goソースコードの読解に基づく)
  • Go言語のチャネルに関する公式ドキュメント