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

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

このコミットは、Go言語のコンパイラ(cmd/gc)の一部であるracewalk.cファイルに対する変更です。racewalk.cは、Goの競合検出器(Race Detector)がプログラムのメモリアクセスを監視するために必要なインストゥルメンテーション(計測コードの挿入)を行う部分に関連しています。

コミット

commit 89bfddbf67e53e95fc70da12d295d781f43666b2
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Wed Nov 14 16:30:53 2012 +0400

    cmd/gc: racewalk: handle OEFACE/OCONVIFACE
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6821096

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

https://github.com/golang/go/commit/89bfddbf67e53e95fc70da12d295d781f43666b2

元コミット内容

cmd/gc: racewalk: handle OEFACE/OCONVIFACE

このコミットは、Goコンパイラの競合検出器のウォーカー(racewalk)が、OEFACEおよびOCONVIFACEというAST(抽象構文木)ノードタイプを適切に処理するように修正するものです。

変更の背景

Go言語には、並行処理におけるデータ競合(data race)を検出するための組み込みの競合検出器があります。これは、プログラムの実行中にメモリへのアクセスを監視し、複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも一方が書き込み操作である場合に警告を発します。

この競合検出器は、コンパイル時に特定のインストゥルメンテーションコードを挿入することで機能します。cmd/gcディレクトリにあるracewalk.cファイルは、このインストゥルメンテーションプロセスの一部を担っています。具体的には、プログラムのASTを走査し、競合検出器が必要とするコード(例えば、メモリ読み書きの前にロックを取得するようなコード)を挿入する役割があります。

以前のバージョンでは、OEFACE(Empty Interfaceの構築)とOCONVIFACE(Concrete型からInterface型への変換)という特定のASTノードタイプがracewalk.cで適切に処理されていませんでした。これらの操作はメモリの読み書きを伴う可能性があり、競合検出器がそれらを監視しないと、潜在的なデータ競合を見逃す可能性がありました。

このコミットは、これらのノードタイプが競合検出器の対象となるように修正し、競合検出器の網羅性と正確性を向上させることを目的としています。また、同時に、競合検出器の処理が不要な一部のASTノードタイプ(OFOR, OIF, OCALLMETH, ORETURN, OSELECT, OEMPTY)に対する冗長なgoto ret;(処理をスキップする)ケースを削除し、コードの整理も行っています。これらのノードは、それ自体が直接的なメモリ操作を伴わないか、あるいはその子ノードが既に適切に処理されるため、明示的にスキップする必要がありませんでした。

前提知識の解説

Go言語の競合検出器 (Race Detector)

Goの競合検出器は、並行プログラムにおけるデータ競合を特定するためのツールです。データ競合は、複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つのアクセスが書き込みである場合に発生します。これは予測不能な動作やバグの原因となります。競合検出器は、プログラムをgo run -racego build -racego test -raceなどのフラグを付けて実行することで有効になります。コンパイラは、メモリアクセスを監視するための追加コードを生成し、実行時に競合を検出します。

抽象構文木 (Abstract Syntax Tree, AST)

ASTは、ソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラは、ソースコードを解析してASTを構築し、そのASTを基に最適化やコード生成を行います。Goコンパイラ(cmd/gc)も同様にASTを使用し、各ノードはプログラムの特定の要素(変数宣言、関数呼び出し、演算子など)を表します。

cmd/gc

cmd/gcは、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する主要なツールであり、最適化、型チェック、コード生成など、コンパイルプロセスの様々な段階を担っています。

racewalk.c

racewalk.cは、Goコンパイラの一部であり、競合検出器のインストゥルメンテーションに関連する処理を行います。具体的には、ASTを走査し、メモリの読み書きが行われる可能性のある箇所に、競合検出器が監視するためのフック(追加コード)を挿入します。この走査は、racewalknode関数によって行われます。

OEFACEOCONVIFACE

これらはGoコンパイラの内部で使われるASTノードのオペレーションコード(Opcode)です。

  • OEFACE (Opcode for Empty Interface): 空のインターフェース型(interface{})の値を構築する操作を表します。例えば、var i interface{} = "hello"のような代入が行われる際に生成される可能性があります。この操作は、内部的に値のコピーやメモリ割り当てを伴うため、競合検出器の監視対象となるべきです。
  • OCONVIFACE (Opcode for Convert to Interface): 具体的な型(concrete type)の値をインターフェース型に変換する操作を表します。例えば、var r io.Reader = os.Stdinのように、os.Stdin*os.File型)がio.Readerインターフェースに変換される際に生成されます。この変換も、値のコピーや内部的なデータ構造の構築を伴うため、メモリ操作として競合検出器の監視対象となるべきです。

技術的詳細

racewalk.c内のracewalknode関数は、ASTノードを再帰的に走査し、競合検出器のインストゥルメンテーションが必要な箇所を特定します。この関数は、ノードのオペレーションコード(n->op)に基づいて異なる処理を行います。

このコミットの主要な変更点は以下の通りです。

  1. OEFACEOCONVIFACEの明示的な処理の追加:

    • 以前は、これらのノードタイプはracewalknode関数内で明示的に処理されていませんでした。
    • コミットにより、OEFACEの場合にはn->leftn->rightの子ノードを再帰的にracewalknodeで走査するように変更されました。これは、空のインターフェースの構築が、その構成要素(値と型情報)のメモリ操作を伴うためです。
    • OCONVIFACEは、以前はyyerror("racewalk: %O must be lowered by now", n->op);というエラーを発生させるケースに含まれていましたが、これはASTのより後の段階で処理されるべきノードであることを示していました。しかし、競合検出器の観点からは、この変換操作自体がメモリ操作を伴うため、racewalkの段階で適切に処理されるべきであると判断され、明示的に処理対象に追加されました。
  2. 冗長なgoto ret;ケースの削除と整理:

    • OFOR, OIF, OCALLMETH, ORETURN, OSELECT, OEMPTYといったノードタイプは、以前はracewalknode関数の冒頭近くでgoto ret;によって処理をスキップしていました。これは、これらのノード自体が直接的なメモリ操作を伴わないか、あるいはその子ノードが既に適切に処理されるため、競合検出器のインストゥルメンテーションが不要であると判断されていたためです。
    • このコミットでは、これらのケースを冒頭から削除し、代わりにファイルの後半にある「// just do generic traversal」というコメントの下にまとめて移動しました。これにより、コードの可読性が向上し、競合検出器が処理すべきノードとそうでないノードの区別がより明確になりました。これらのノードは、一般的なAST走査の一部として扱われるべきであり、特別なスキップロジックは不要であるという判断です。

これらの変更により、Goの競合検出器は、インターフェースの構築や変換といった、これまで見落とされていた可能性のあるメモリ操作も適切に監視できるようになり、検出能力が向上しました。

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

src/cmd/gc/racewalk.cファイルが変更されています。

--- a/src/cmd/gc/racewalk.c
+++ b/src/cmd/gc/racewalk.c
@@ -153,12 +153,6 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\n 		racewalknode(&n->left, init, 0, 0);\n 		goto ret;\n 
-	case OFOR:
-		goto ret;
-
-	case OIF:
-		goto ret;
-
 	case OPROC:
 		racewalknode(&n->left, init, 0, 0);\n 		goto ret;\n@@ -171,24 +165,12 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\n 		racewalknode(&n->left, init, 0, 0);\n 		goto ret;\n 
-	case OCALLMETH:
-		goto ret;
-
-	case ORETURN:
-		goto ret;
-
-	case OSELECT:
-		goto ret;
-
 	case OSWITCH:
 		if(n->ntest->op == OTYPESW)
 			// TODO(dvyukov): the expression can contain calls or reads.
 			return;
 		goto ret;
 
-	case OEMPTY:
-		goto ret;
-
 	case ONOT:
 	case OMINUS:
 	case OPLUS:
@@ -299,6 +281,11 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\n 		racewalknode(&n->left, init, 0, 1);\n 		goto ret;\n 
+	case OEFACE:
+		racewalknode(&n->left, init, 0, 0);\n+		racewalknode(&n->right, init, 0, 0);\n+		goto ret;\n+
 	// should not appear in AST by now
 	case OSEND:
 	case ORECV:
@@ -309,9 +296,19 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\n 	case OCASE:\n 	case OPANIC:\n 	case ORECOVER:\n+\tcase OCONVIFACE:\n 	\tyyerror(\"racewalk: %O must be lowered by now\", n->op);\n 	\tgoto ret;\n \n+\t// just do generic traversal\n+\tcase OFOR:\n+\tcase OIF:\n+\tcase OCALLMETH:\n+\tcase ORETURN:\n+\tcase OSELECT:\n+\tcase OEMPTY:\n+\t\tgoto ret;\n+\n 	// does not require instrumentation\n 	case OINDEXMAP:  // implemented in runtime\n 	case OPRINT:     // don't bother instrumenting it\n@@ -340,7 +337,6 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\n 	case OCLOSURE:\n 	case ODOTTYPE:\n 	case ODOTTYPE2:\n-\tcase OCONVIFACE:\n 	case OCALL:\n 	case OBREAK:\n 	case ODCL:\n@@ -357,7 +353,6 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\n 	case OINDREG:\n 	case OCOM:\n 	case ODOTMETH:\n-\tcase OEFACE:\n 	case OITAB:\n 	case OEXTEND:\n 	case OHMUL:\n```

## コアとなるコードの解説

上記の差分は、`racewalknode`関数におけるASTノードの処理ロジックの変更を示しています。

1.  **`OFOR`, `OIF`, `OCALLMETH`, `ORETURN`, `OSELECT`, `OEMPTY` の移動**:
    *   これらの`case`文は、ファイルの冒頭近く(行153-171付近)から削除されています。
    *   代わりに、ファイルの後半(行309付近)にある「`// just do generic traversal`」というコメントの下に移動され、まとめて`goto ret;`で処理をスキップするように変更されています。これは、これらのノードタイプが競合検出器による特別なインストゥルメンテーションを必要とせず、一般的なAST走査の一部として扱われるべきであることを示しています。

2.  **`OEFACE` の追加**:
    *   行299付近に新しい`case OEFACE:`ブロックが追加されています。
    *   このブロックでは、`racewalknode(&n->left, init, 0, 0);`と`racewalknode(&n->right, init, 0, 0);`が呼び出されています。これは、`OEFACE`ノードが持つ左の子ノードと右の子ノード(通常、構築されるインターフェースの値と型情報に対応)を再帰的に走査し、それらのメモリ操作も競合検出器の監視対象とすることを示しています。

3.  **`OCONVIFACE` の移動と処理**:
    *   以前は、`OCONVIFACE`は「`// should not appear in AST by now`」というコメントの下にあるエラーを発生させる`case`ブロック(行309付近)に含まれていました。これは、`racewalk`の段階ではこのノードは既に下位レベルの表現に変換されているべきである、という前提があったためです。
    *   しかし、このコミットでは、`OCONVIFACE`がこのエラーブロックから削除され、代わりに`OEFACE`と同様に競合検出器の監視対象として明示的に処理されるようになりました。これにより、インターフェース変換に伴うメモリ操作も適切に検出されるようになります。

これらの変更は、Goの競合検出器がより多くの種類のメモリ操作を正確に監視できるようにするための重要な改善であり、特にインターフェースの利用が多いGoプログラムにおけるデータ競合の検出能力を向上させます。

## 関連リンク

*   Go言語の競合検出器に関する公式ドキュメント: [https://go.dev/doc/articles/race_detector](https://go.dev/doc/articles/race_detector)
*   Goコンパイラのソースコード(`cmd/gc`): [https://github.com/golang/go/tree/master/src/cmd/compile](https://github.com/golang/go/tree/master/src/cmd/compile) (Go 1.5以降、`cmd/gc`は`cmd/compile`に統合されましたが、概念は同じです)

## 参考にした情報源リンク

*   Go言語のソースコード
*   Go言語の競合検出器に関する公式ドキュメント
*   Goコンパイラの内部構造に関する一般的な知識
*   AST (Abstract Syntax Tree) に関する一般的な情報