[インデックス 14334] ファイルの概要
このコミットは、Go言語のコンパイラ (cmd/gc
) における racewalk
ツールに関するものです。具体的には、racewalk
がヒープから返されるパラメータ (returnsfromheap params
) を適切に計測(instrument)するように修正を加えています。これは、Goプログラムにおけるデータ競合(data race)の検出精度を向上させるための重要な変更です。
コミット
commit a3a7244779066ba639e9c61d4b351800a9cb77f6
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Wed Nov 7 12:10:35 2012 +0400
cmd/gc: racewalk: instrument returnsfromheap params
Fixes #4307.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6822073
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a3a7244779066ba639e9c61d4b351800a9cb77f6
元コミット内容
cmd/gc: racewalk: instrument returnsfromheap params
Fixes #4307.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6822073
変更の背景
このコミットは、Go言語のデータ競合検出ツールである racewalk
の機能改善を目的としています。コミットメッセージにある Fixes #4307
は、GoのIssueトラッカーにおける「Issue 4307: racewalk: a tool to detect data races in Go programs」を指しています。このIssueは、racewalk
ツールがGoプログラム内のデータ競合を検出する際の課題や改善点について議論されたものです。
データ競合は、複数のゴルーチン(goroutine)が同時に同じメモリ位置にアクセスし、少なくとも1つのアクセスが書き込みであり、かつそれらのアクセスが同期メカニズムによって順序付けされていない場合に発生するバグです。データ競合はプログラムの予測不能な動作やクラッシュを引き起こす可能性があり、その検出は並行プログラミングにおいて非常に重要です。
racewalk
は、コンパイル時にコードに計測(instrumentation)コードを挿入することで、実行時にデータ競合を検出するツールです。しかし、特定のケース、特にヒープから返されるパラメータ(returnsfromheap params
)の扱いにおいて、racewalk
が十分な計測を行えていないという問題がありました。この計測の不足が、一部のデータ競合を見逃す原因となっていたと考えられます。
このコミットは、racewalk
がこれらの「ヒープから返されるパラメータ」に対しても適切に計測コードを挿入するように修正することで、データ競合検出の網羅性と精度を高めることを目的としています。
前提知識の解説
データ競合 (Data Race)
データ競合は、並行プログラムにおけるバグの一種です。以下の3つの条件がすべて満たされたときに発生します。
- 複数のゴルーチン(またはスレッド)が同時に同じメモリ位置にアクセスする。
- 少なくとも1つのアクセスが書き込み操作である。
- それらのアクセスが同期メカニズム(ミューテックス、チャネルなど)によって順序付けされていない。 データ競合が発生すると、プログラムの実行結果が非決定論的になり、デバッグが非常に困難になります。
racewalk
racewalk
は、Go言語のコンパイラ (cmd/gc
) に組み込まれたデータ競合検出のための機能です。これは、プログラムのソースコードを解析し、データ競合が発生しうるメモリアクセスに対して追加のコード(計測コード)を挿入します。この計測コードは、実行時にメモリアクセスの履歴を追跡し、データ競合のパターンを検出すると警告を発します。racewalk
は、Goの標準ライブラリに含まれる go test -race
コマンドを通じて利用できます。
cmd/gc
(Go Compiler)
cmd/gc
は、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。racewalk
のようなコンパイル時計測ツールは、このコンパイラの内部で動作し、コード生成の過程で計測コードを挿入します。
計測 (Instrumentation)
計測とは、プログラムの実行時の振る舞いを監視するために、追加のコードを挿入するプロセスです。データ競合検出においては、メモリアクセスやゴルーチンのスケジューリングに関する情報を収集するためのコードが挿入されます。これにより、実行時にデータ競合のパターンを特定することが可能になります。
returnsfromheap params
これは、関数呼び出しにおいて、ヒープ領域に割り当てられたメモリが関数の戻り値として返される場合のパラメータを指します。Goでは、変数がスタックに割り当てられるかヒープに割り当てられるかは、コンパイラの「エスケープ解析(escape analysis)」によって決定されます。変数が関数のスコープを超えて参照される可能性がある場合(例えば、ポインタが返される場合など)、その変数はヒープに割り当てられます。returnsfromheap params
は、このようなヒープに割り当てられた戻り値のパラメータが、データ競合検出の対象となることを示唆しています。
技術的詳細
このコミットの技術的な核心は、racewalk
が関数のエントリポイントとエグジットポイントに挿入する計測コードの配置と、特定のノードタイプに対する計測のスキップロジックの変更にあります。
racewalk
は、各関数の開始時 (fn->enter
) と終了時 (fn->exit
) に、データ競合検出のためのフックを挿入します。これまでの実装では、関数の本体 (curfn->nbody
) の計測が fn->enter
と fn->exit
の設定後に行われていました。
変更前:
racewalklist(fn->nbody, nil);
(関数の本体を計測)fn->enter = concat(list1(nd), fn->enter);
(エントリフックを追加)fn->exit = list(fn->exit, nd);
(エグジットフックを追加)
この順序だと、fn->enter
と fn->exit
自体に含まれる可能性のあるコードが、racewalklist
による計測の対象外となる可能性がありました。
このコミットでは、racewalklist(fn->nbody, nil);
の呼び出しを、fn->enter
と fn->exit
の設定より前に移動しています。
変更後:
racewalklist(fn->nbody, nil);
(関数の本体を計測)racewalklist(fn->nbody, nil);
(再度関数の本体を計測 - これは誤り、実際はfn->nbody
の計測は一度で良いはず)racewalklist(fn->exit, nil);
(関数のエグジット部分を計測)fn->enter = concat(list1(nd), fn->enter);
(エントリフックを追加)fn->exit = list(fn->exit, nd);
(エグジットフックを追加)
訂正: 実際の変更は、racewalklist(fn->nbody, nil);
の呼び出しが fn->enter
と fn->exit
の設定より前に移動し、さらに fn->enter
と fn->exit
自体も racewalklist
で計測されるようになった点です。
具体的には、以下の行が追加されました。
+ racewalklist(fn->nbody, nil);
+ // nothing interesting for race detector in fn->enter
+ racewalklist(fn->exit, nil);
そして、既存の racewalklist(curfn->nbody, nil);
が削除されています。これにより、関数の本体、そして関数のエグジット部分が明示的に racewalklist
によって計測されるようになりました。fn->enter
についてはコメントで「race detectorにとって興味深いものはない」とされていますが、これは fn->enter
に挿入されるコードが通常、データ競合を引き起こすようなメモリアクセスを含まないためと考えられます。
さらに、racewalknode
関数内の計測をスキップするノードタイプに OPARAM
が追加されました。
- case OPRINT: // don't bother instrumenting it
- case OPRINTN: // don't bother instrumenting it
+ case OPRINT: // don't bother instrumenting it
+ case OPRINTN: // don't bother instrumenting it
+ case OPARAM: // it appears only in fn->exit to copy heap params back
OPARAM
は、関数のパラメータを表すノードタイプです。コメントにあるように、「fn->exit
にのみ現れ、ヒープパラメータをコピーバックする」とあります。これは、関数からヒープに割り当てられた戻り値が返される際に、その値が適切に処理されることを保証するための操作に関連していると考えられます。この OPARAM
ノードを計測対象から除外することで、不要な計測オーバーヘッドを避けつつ、必要な計測は fn->exit
全体の計測によってカバーされるという設計意図が読み取れます。
デバッグ出力 (debug['W']
) の部分も修正されており、curfn
ではなく fn
を使用するように変更されています。これは、racewalk
関数が引数として受け取る fn
(現在の関数ノード)を正しく参照するための修正です。
コアとなるコードの変更箇所
src/cmd/gc/racewalk.c
ファイルの変更点です。
--- a/src/cmd/gc/racewalk.c
+++ b/src/cmd/gc/racewalk.c
@@ -45,6 +45,10 @@ racewalk(Node *fn)
}
}
+ racewalklist(fn->nbody, nil);
+ // nothing interesting for race detector in fn->enter
+ racewalklist(fn->exit, nil);
+
// nodpc is the PC of the caller as extracted by
// getcallerpc. We use -widthptr(FP) for x86.
// BUG: this will not work on arm.
@@ -56,15 +60,14 @@ racewalk(Node *fn)
fn->enter = concat(list1(nd), fn->enter);
nd = mkcall("racefuncexit", T, nil);
fn->exit = list(fn->exit, nd);
-\tracewalklist(curfn->nbody, nil);
if(debug['W']) {
-\t\tsnprint(s, sizeof(s), "after racewalk %S", curfn->nname->sym);\n-\t\tdumplist(s, curfn->nbody);\n-\t\tsnprint(s, sizeof(s), "after walk %S", curfn->nname->sym);\n-\t\tdumplist(s, curfn->nbody);\n-\t\tsnprint(s, sizeof(s), "enter %S", curfn->nname->sym);\n-\t\tdumplist(s, curfn->enter);\n+\t\tsnprint(s, sizeof(s), "after racewalk %S", fn->nname->sym);\n+\t\tdumplist(s, fn->nbody);\n+\t\tsnprint(s, sizeof(s), "enter %S", fn->nname->sym);\n+\t\tdumplist(s, fn->enter);\n+\t\tsnprint(s, sizeof(s), "exit %S", fn->nname->sym);\n+\t\tdumplist(s, fn->exit);\n }
}
@@ -311,8 +314,9 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)
// does not require instrumentation
case OINDEXMAP: // implemented in runtime
-\tcase OPRINT: // don't bother instrumenting it
-\tcase OPRINTN: // don't bother instrumenting it
+\tcase OPRINT: // don't bother instrumenting it
+\tcase OPRINTN: // don't bother instrumenting it
+\tcase OPARAM: // it appears only in fn->exit to copy heap params back
\tgoto ret;
// unimplemented
コアとなるコードの解説
-
racewalk
関数の変更:racewalklist(fn->nbody, nil);
が追加され、関数の本体が最初に計測されるようになりました。racewalklist(fn->exit, nil);
が追加され、関数の終了処理部分も明示的に計測されるようになりました。これにより、関数がヒープから値を返す際の処理(returnsfromheap params
)がデータ競合検出の対象に含まれるようになります。- 以前の
racewalklist(curfn->nbody, nil);
は削除されました。これは、新しい配置でより適切に計測が行われるためです。 - デバッグ出力の
snprint
およびdumplist
の呼び出しで、curfn
の代わりにfn
が使用されるようになりました。これは、racewalk
関数が引数として受け取るfn
が現在の関数ノードを正確に指すため、より正確なデバッグ情報を提供します。また、fn->exit
のデバッグ出力も追加され、終了処理部分の計測状況も確認できるようになりました。
-
racewalknode
関数の変更:case OPARAM:
が追加され、OPARAM
ノードが計測をスキップする対象となりました。OPARAM
は関数のパラメータを表し、特に「fn->exit
にのみ現れ、ヒープパラメータをコピーバックする」という性質を持つため、このノード自体を直接計測する必要がないと判断されたと考えられます。これは、fn->exit
全体の計測によって、関連するメモリ操作がカバーされるため、冗長な計測を避けるための最適化である可能性があります。
これらの変更により、racewalk
は関数の戻り値としてヒープから返されるデータに対しても、より網羅的にデータ競合の検出を行うことができるようになり、Goプログラムの信頼性向上に貢献します。
関連リンク
- Go Issue 4307: https://github.com/golang/go/issues/4307
- Go CL 6822073: https://golang.org/cl/6822073
参考にした情報源リンク
- Go言語のIssueトラッカー (GitHub)
- Go言語のコードレビューシステム (Gerrit)
- データ競合に関する一般的な情報源 (並行プログラミング、Go言語のドキュメントなど)
- Go言語のコンパイラ (
cmd/gc
) の内部構造に関する情報源 (Goのソースコード、関連する設計ドキュメントなど)