[インデックス 15967] ファイルの概要
このコミットは、Go言語のコンパイラ(cmd/gc
)におけるデータ競合検出器(Race Detector)のインストゥルメンテーションに関するバグ修正と機能強化を目的としています。具体的には、append
関数と type switch
文におけるデータ競合の検出が正しく行われるように、コンパイラのコードウォークロジックが修正されました。これにより、これらのGo言語の構文を使用する際に発生しうる潜在的なデータ競合を見逃すリスクが低減されます。
コミット
commit 7c79910cb9abbac2f525c2b2ecad188ff27eb8af
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Wed Mar 27 20:33:17 2013 +0100
cmd/gc: fix race instrumentation of append and type switches.
The remaining issues are about runtime and sync package
instrumentation.
Update #4228
R=dvyukov, bradfitz
CC=golang-dev
https://golang.org/cl/8041043
---
src/cmd/gc/racewalk.c | 25 ++++++++--------
src/pkg/runtime/race/testdata/mop_test.go | 46 +++++++++++++++++++++++++++++
src/pkg/runtime/race/testdata/slice_test.go | 3 +-\
3 files changed, 59 insertions(+), 15 deletions(-)
diff --git a/src/cmd/gc/racewalk.c b/src/cmd/gc/racewalk.c
index fee5cf4226..5d4f62e761 100644
--- a/src/cmd/gc/racewalk.c
+++ b/src/cmd/gc/racewalk.c
@@ -133,7 +133,6 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)
case OASOP:
case OAS:
case OAS2:
-\tcase OAS2DOTTYPE:
case OAS2RECV:
case OAS2FUNC:
case OAS2MAPR:
@@ -186,12 +185,6 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)
\t\tracewalknode(&n->left, init, 0, 0);\n \t\tgoto ret;\n \n-\tcase OSWITCH:\n-\t\tif(n->ntest->op == OTYPESW)\n-\t\t\t// TODO(dvyukov): the expression can contain calls or reads.\n-\t\t\treturn;\n-\t\tgoto ret;\n-\n \tcase ONOT:\n \tcase OMINUS:\n \tcase OPLUS:\
@@ -317,6 +310,10 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\
\t\tracewalknode(&n->left, init, 0, 0);\n \t\tgoto ret;\n \n+\tcase OTYPESW:\n+\t\tracewalknode(&n->right, init, 0, 0);\n+\t\tgoto ret;\n+\n \t// should not appear in AST by now\n \tcase OSEND:\n \tcase ORECV:\
@@ -334,6 +331,7 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\
\tcase OMAKESLICE:\
\tcase OCALL:\
\tcase OCOPY:\
+\tcase OAPPEND:\
\tcase ORUNESTR:\
\tcase OARRAYBYTESTR:\
\tcase OARRAYRUNESTR:\
@@ -344,6 +342,7 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\
\tcase OADDSTR:\
\tcase ODOTTYPE:\
\tcase ODOTTYPE2:\
+\tcase OAS2DOTTYPE:\
\tcase OCALLPART: // lowered to PTRLIT\
\tcase OCLOSURE: // lowered to PTRLIT\
\tcase ORANGE: // lowered to ordinary for loop\
@@ -364,6 +363,7 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\
\tcase OIF:\
\tcase OCALLMETH:\
\tcase ORETURN:\
+\tcase OSWITCH:\
\tcase OSELECT:\
\tcase OEMPTY:\
\tcase OBREAK:\
@@ -389,10 +389,6 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\
\tcase OLITERAL:\
\tcase OSLICESTR: // always preceded by bounds checking, avoid double instrumentation.\
\t\tgoto ret;\n-\n-\t// unimplemented\n-\tcase OAPPEND:\
-\t\tgoto ret;\n \t}\n \n ret:\
@@ -448,6 +444,7 @@ callinstr(Node **np, NodeList **init, int wr, int skip)\
\tif(isartificial(n))\
\t\treturn 0;\
\tif(t->etype == TSTRUCT) {\
+\t\t// TODO: instrument arrays similarly.\
\t\t// PARAMs w/o PHEAP are not interesting.\
\t\tif(n->class == PPARAM || n->class == PPARAMOUT)\
\t\t\treturn 0;\
@@ -484,7 +481,7 @@ callinstr(Node **np, NodeList **init, int wr, int skip)\
\t// that has got a pointer inside. Whether it points to\
\t// the heap or not is impossible to know at compile time\
\tif((class&PHEAP) || class == PPARAMREF || class == PEXTERN\
-\t\t|| b->type->etype == TARRAY || b->op == ODOTPTR || b->op == OIND || b->op == OXDOT) {\
+\t\t|| b->op == OINDEX || b->op == ODOTPTR || b->op == OIND || b->op == OXDOT) {\
\t\thascalls = 0;\
\t\tforeach(n, hascallspred, &hascalls);\
\t\tif(hascalls) {\
@@ -510,6 +507,8 @@ uintptraddr(Node *n)\
\treturn r;\
}\n \n+// basenod returns the simplest child node of n pointing to the same\n+// memory area.\n static Node*\n basenod(Node *n)\
{\n@@ -518,7 +517,7 @@ basenod(Node *n)\
\t\t\tn = n->left;\
\t\t\tcontinue;\
\t\t}\n-\t\tif(n->op == OINDEX) {\
+\t\tif(n->op == OINDEX && isfixedarray(n->type)) {\
\t\t\tn = n->left;\
\t\t\tcontinue;\
\t\t}\ndiff --git a/src/pkg/runtime/race/testdata/mop_test.go b/src/pkg/runtime/race/testdata/mop_test.go
index 1a7ed96249..ae70cbb5f8 100644
--- a/src/pkg/runtime/race/testdata/mop_test.go
+++ b/src/pkg/runtime/race/testdata/mop_test.go
@@ -227,6 +227,37 @@ func TestRaceCaseFallthrough(t *testing.T) {\
\t<-ch\n }\n \n+func TestRaceCaseType(t *testing.T) {\n+\tvar x, y int\n+\tvar i interface{} = x\n+\tc := make(chan int, 1)\n+\tgo func() {\n+\t\tswitch i.(type) {\n+\t\tcase nil:\n+\t\tcase int:\n+\t\t}\n+\t\tc <- 1\n+\t}()\n+\ti = y\n+\t<-c\n+}\n+\n+func TestRaceCaseTypeBody(t *testing.T) {\n+\tvar x, y int\n+\tvar i interface{} = &x\n+\tc := make(chan int, 1)\n+\tgo func() {\n+\t\tswitch i := i.(type) {\n+\t\tcase nil:\n+\t\tcase *int:\n+\t\t\t*i = y\n+\t\t}\n+\t\tc <- 1\n+\t}()\n+\tx = y\n+\t<-c\n+}\n+\n func TestNoRaceRange(t *testing.T) {\
\tch := make(chan int, 3)\
\ta := [...]int{1, 2, 3}\
\n@@ -1446,6 +1477,21 @@ func TestRaceFailingSliceStruct(t *testing.T) {\
\t<-c\n }\n \n+func TestRaceAppendSliceStruct(t *testing.T) {\n+\ttype X struct {\n+\t\tx, y int\n+\t}\n+\tc := make(chan bool, 1)\n+\tx := make([]X, 10)\n+\tgo func() {\n+\t\ty := make([]X, 0, 10)\n+\t\ty = append(y, x...)\n+\t\tc <- true\n+\t}()\n+\tx[1].y = 42\n+\t<-c\n+}\n+\n func TestRaceStructInd(t *testing.T) {\
\tc := make(chan bool, 1)\
\ttype Item struct {\ndiff --git a/src/pkg/runtime/race/testdata/slice_test.go b/src/pkg/runtime/race/testdata/slice_test.go
index 1fe051b121..c85df5e3d6 100644
--- a/src/pkg/runtime/race/testdata/slice_test.go
+++ b/src/pkg/runtime/race/testdata/slice_test.go
@@ -338,8 +338,7 @@ func TestRaceSliceVarCopy2(t *testing.T) {\
\t<-c\n }\n \n-// Not implemented.\n-func TestRaceFailingSliceAppend(t *testing.T) {\
+func TestRaceSliceAppend(t *testing.T) {\
\tc := make(chan bool, 1)\
\ts := make([]int, 10, 20)\
\tgo func() {\
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7c79910cb9abbac2f525c2b2ecad188ff27eb8af
元コミット内容
cmd/gc: fix race instrumentation of append and type switches.
The remaining issues are about runtime and sync package
instrumentation.
Update #4228
R=dvyukov, bradfitz
CC=golang-dev
https://golang.org/cl/8041043
変更の背景
Go言語は並行処理を強力にサポートしていますが、並行処理を行う上で最も厄介な問題の一つが「データ競合(Data Race)」です。データ競合とは、複数のゴルーチンが同時に同じメモリ領域にアクセスし、少なくとも一方のアクセスが書き込みであり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に発生します。データ競合は予測不能なプログラムの振る舞いやバグ(例: クラッシュ、不正なデータ、デッドロック)を引き起こす可能性があり、その特定とデバッグは非常に困難です。
Go言語には、このようなデータ競合をコンパイル時または実行時に検出するための「Race Detector」が組み込まれています。これは、プログラムの実行中にメモリアクセスを監視し、競合のパターンを検出すると警告を発するツールです。Race Detectorは、コンパイラが生成するコードに特別なインストゥルメンテーション(計測コード)を挿入することで機能します。
しかし、このコミット以前のRace Detectorの実装では、特定のGo言語の構文、特に append
関数と type switch
文において、インストゥルメンテーションが不完全であったり、全く行われていなかったりする問題がありました。これにより、これらの構文内で発生するデータ競合を見逃してしまう可能性がありました。
この問題は、GoのIssueトラッカーで #4228 として報告されていました。append
はスライスの容量が不足した場合に新しい基底配列を割り当てて要素をコピーするため、内部的にメモリの読み書きが発生します。type switch
はインターフェースの値の型を動的にチェックし、それに応じて異なるコードパスを実行するため、インターフェースの基底値へのアクセスが発生します。これらの操作が並行して行われる場合、データ競合が発生する可能性があり、Race Detectorがそれを検出できないことは大きな欠陥でした。
このコミットは、これらの特定のケースにおけるRace Detectorの精度と網羅性を向上させ、より堅牢な並行プログラムのデバッグを可能にすることを目的としています。
前提知識の解説
データ競合 (Data Race)
データ競合は、並行プログラミングにおける深刻なバグの一種です。以下の3つの条件がすべて満たされたときに発生します。
- 複数のゴルーチンが同じメモリ領域にアクセスする: 2つ以上のゴルーチンが、同じ変数やデータ構造を操作しようとします。
- 少なくとも一方のアクセスが書き込みである: アクセスのうち少なくとも1つが、メモリの内容を変更する操作(書き込み)である必要があります。両方が読み込みの場合は競合とはみなされません。
- アクセスが同期メカニズムによって保護されていない: ミューテックス、チャネル、アトミック操作などの同期プリミティブが適切に使用されていないため、アクセス順序が保証されません。
データ競合が発生すると、プログラムの実行結果がアクセス順序に依存するようになり、非決定的な振る舞いを引き起こします。これは、デバッグが非常に困難な「Heisenbug」(観測しようとすると消えるバグ)の典型例です。
Go Race Detector
Go言語に組み込まれているRace Detectorは、データ競合を検出するための強力なツールです。Goプログラムを -race
フラグ付きでコンパイルすると、コンパイラはメモリアクセスを監視するための追加のコード(インストゥルメンテーション)を生成します。実行時に、このインストゥルメンテーションコードがメモリの読み書きを記録し、競合パターン(例: あるゴルーチンが書き込み、別のゴルーチンが読み書き)を検出すると、詳細なレポート(スタックトレース、アクセスされたメモリ位置など)を出力します。
Race Detectorは、コンパイル時にAST(抽象構文木)をウォークし、メモリアクセスが発生する可能性のある箇所に計測コードを挿入します。このコミットの racewalk.c
は、そのインストゥルメンテーションロジックの中核を担うファイルです。
append
関数
append
はGoのスライスを操作するための組み込み関数です。スライスに要素を追加するために使用されます。append
の重要な特性は、追加後のスライスの容量が元のスライスの容量を超える場合、Goランタイムがより大きな新しい基底配列を割り当て、元の要素を新しい配列にコピーし、その後新しい要素を追加するという動作です。
この「新しい配列の割り当てとコピー」のプロセスは、内部的にメモリの読み書きを伴います。もし複数のゴルーチンが同時に同じスライスに対して append
を実行したり、append
が行われているスライスの基底配列に他のゴルーチンがアクセスしたりすると、データ競合が発生する可能性があります。
type switch
文
type switch
は、インターフェース変数が保持する具体的な型に基づいて異なるコードブロックを実行するためのGoの制御構造です。
var i interface{} = someValue
switch v := i.(type) {
case int:
// i は int 型
case string:
// i は string 型
default:
// その他の型
}
type switch
の内部では、インターフェースの値(型情報と基底値)へのアクセスが発生します。もしインターフェース変数が複数のゴルーチン間で共有され、type switch
が実行されている間に別のゴルーチンがそのインターフェース変数を変更すると、データ競合が発生する可能性があります。
Goコンパイラ (cmd/gc
) と AST (Abstract Syntax Tree)
Goコンパイラ(cmd/gc
)は、Goのソースコードを機械語に変換する主要なツールチェーンの一部です。コンパイルプロセスでは、ソースコードはまずAST(抽象構文木)にパースされます。ASTはプログラムの構造を木構造で表現したもので、各ノードはプログラムの要素(変数宣言、関数呼び出し、演算子、制御構造など)を表します。
src/cmd/gc/racewalk.c
ファイルは、このASTをトラバース(ウォーク)し、データ競合検出に必要なインストゥルメンテーションコードを挿入する役割を担っています。ASTノードには、OASOP
(代入演算子)、OTYPESW
(type switch)、OAPPEND
(append関数呼び出し)、OINDEX
(配列/スライスインデックスアクセス)など、様々な種類があります。racewalknode
関数は、これらのノードの種類に応じて異なる処理を行い、必要に応じてメモリアクセス監視のための関数呼び出しを挿入します。
技術的詳細
このコミットの技術的な核心は、src/cmd/gc/racewalk.c
ファイルにおけるASTウォークロジックの変更にあります。以前は append
や type switch
がRace Detectorのインストゥルメンテーションの対象外であったり、不適切に処理されていたりしたため、それらを正しく検出できるように修正が加えられました。
-
type switch
のインストゥルメンテーションの修正:- 以前の
racewalknode
関数では、OSWITCH
ノード(一般的なswitch
文)の処理において、もしそれがOTYPESW
(type switch)であれば、インストゥルメンテーションをスキップするreturn
文がありました。これは「TODO(dvyukov): the expression can contain calls or reads.」というコメントと共に、未実装の課題として残されていました。 - このコミットでは、この
OSWITCH
の特殊なスキップロジックが削除されました。 - 代わりに、
OTYPESW
ノードがracewalknode
の別のcase
として明示的に追加され、racewalknode(&n->right, init, 0, 0);
を通じてtype switch
の右辺(インターフェース変数)がウォークされるようになりました。これにより、type switch
の評価時に発生するインターフェース値へのアクセスがRace Detectorの監視対象となります。 - また、
OAS2DOTTYPE
(type assertion を伴う多重代入)も、以前はOASOP
などの代入演算子群から除外されていましたが、新たにインストゥルメンテーション対象のノードリストに追加されました。これは、type switch
のv := i.(type)
のような構文が内部的にOAS2DOTTYPE
に変換される場合があるため、その代入操作も監視する必要があるためです。
- 以前の
-
append
関数のインストゥルメンテーションの有効化:- 以前の
racewalknode
関数には、OAPPEND
ノードに対して「// unimplemented」というコメントと共にgoto ret;
があり、インストゥルメンテーションがスキップされていました。 - このコミットでは、この
OAPPEND
のスキップロジックが削除され、OAPPEND
がOMAKESLICE
,OCALL
,OCOPY
などと同様に、通常のインストゥルメンテーション対象のノードとして扱われるようになりました。これにより、append
関数が内部的に行うメモリの読み書き(特にスライスの再割り当てと要素のコピー)がRace Detectorによって監視されるようになります。
- 以前の
-
basenod
関数の修正:basenod
関数は、与えられたノードが指すメモリ領域の「基底」となるノード(例:a[i]
のa
)を特定するために使用されます。これは、Race Detectorがメモリ競合を正確に報告するために、アクセスされたメモリの「どこ」が競合しているのかを特定する上で重要です。- 以前の
basenod
では、OINDEX
(配列/スライスインデックスアクセス)の場合に無条件にn->left
(基底配列/スライス)を返していました。 - このコミットでは、
if(n->op == OINDEX && isfixedarray(n->type))
という条件が追加されました。これは、OINDEX
が固定長配列([N]T
)へのアクセスである場合にのみn->left
を返すように変更されたことを意味します。スライス([]T
)は動的なサイズを持つため、その基底配列は実行時に変更される可能性があり、固定長配列とは異なる扱いが必要です。この変更により、Race Detectorがスライス操作におけるメモリの基底をより正確に追跡できるようになります。
-
callinstr
関数の修正:callinstr
関数は、特定のノードに対してインストゥルメンテーションが必要かどうかを判断するロジックを含んでいます。b->type->etype == TARRAY
という条件がb->op == OINDEX
に変更されました。これは、配列型そのものよりも、配列へのインデックスアクセス操作(OINDEX
)を直接チェックする方が、インストゥルメンテーションの対象をより正確に絞り込むことができるためと考えられます。
これらの変更により、GoのRace Detectorは append
や type switch
を含むより広範なGoの構文におけるデータ競合を検出できるようになり、並行プログラムの信頼性が向上します。
コアとなるコードの変更箇所
src/cmd/gc/racewalk.c
における主要な変更箇所を抜粋します。
--- a/src/cmd/gc/racewalk.c
+++ b/src/cmd/gc/racewalk.c
@@ -133,7 +133,6 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)
case OASOP:
case OAS:
case OAS2:
-\tcase OAS2DOTTYPE:
case OAS2RECV:
case OAS2FUNC:
case OAS2MAPR:
@@ -186,12 +185,6 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)
\t\tracewalknode(&n->left, init, 0, 0);\n \t\tgoto ret;\n \n-\tcase OSWITCH:\
-\t\tif(n->ntest->op == OTYPESW)\
-\t\t\t// TODO(dvyukov): the expression can contain calls or reads.\
-\t\t\treturn;\
-\t\tgoto ret;\
-\n \tcase ONOT:\
\tcase OMINUS:\
\tcase OPLUS:\
@@ -317,6 +310,10 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\
\t\tracewalknode(&n->left, init, 0, 0);\n \t\tgoto ret;\n \n+\tcase OTYPESW:\
+\t\tracewalknode(&n->right, init, 0, 0);\n+\t\tgoto ret;\
+\n \t// should not appear in AST by now\
\tcase OSEND:\
\tcase ORECV:\
@@ -334,6 +331,7 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\
\tcase OMAKESLICE:\
\tcase OCALL:\
\tcase OCOPY:\
+\tcase OAPPEND:\
\tcase ORUNESTR:\
\tcase OARRAYBYTESTR:\
\tcase OARRAYRUNESTR:\
@@ -344,6 +342,7 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\
\tcase OADDSTR:\
\tcase ODOTTYPE:\
\tcase ODOTTYPE2:\
+\tcase OAS2DOTTYPE:\
\tcase OCALLPART: // lowered to PTRLIT\
\tcase OCLOSURE: // lowered to PTRLIT\
\tcase ORANGE: // lowered to ordinary for loop\
@@ -364,6 +363,7 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\
\tcase OIF:\
\tcase OCALLMETH:\
\tcase ORETURN:\
+\tcase OSWITCH:\
\tcase OSELECT:\
\tcase OEMPTY:\
\tcase OBREAK:\
@@ -389,10 +389,6 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)\
\tcase OLITERAL:\
\tcase OSLICESTR: // always preceded by bounds checking, avoid double instrumentation.\
\t\tgoto ret;\n-\n-\t// unimplemented\
-\tcase OAPPEND:\
-\t\tgoto ret;\
\t}\
\n ret:\
@@ -484,7 +481,7 @@ callinstr(Node **np, NodeList **init, int wr, int skip)\
\t// that has got a pointer inside. Whether it points to\
\t// the heap or not is impossible to know at compile time\
\tif((class&PHEAP) || class == PPARAMREF || class == PEXTERN\
-\t\t|| b->type->etype == TARRAY || b->op == ODOTPTR || b->op == OIND || b->op == OXDOT) {\
+\t\t|| b->op == OINDEX || b->op == ODOTPTR || b->op == OIND || b->op == OXDOT) {\
\t\thascalls = 0;\
\t\tforeach(n, hascallspred, &hascalls);\
\t\tif(hascalls) {\
@@ -518,7 +517,7 @@ basenod(Node *n)\
\t\t\tn = n->left;\
\t\t\tcontinue;\
\t\t}\n-\t\tif(n->op == OINDEX) {\
+\t\tif(n->op == OINDEX && isfixedarray(n->type)) {\
\t\t\tn = n->left;\
\t\t\tcontinue;\
\t\t}\
コアとなるコードの解説
上記の差分は、src/cmd/gc/racewalk.c
内の racewalknode
、callinstr
、basenod
関数の変更を示しています。
-
racewalknode
関数の変更:- case OAS2DOTTYPE:
の削除:- 以前は
OASOP
,OAS
,OAS2
などの代入演算子群の中にOAS2DOTTYPE
(型アサーションを伴う多重代入)が含まれていましたが、ここから削除されました。これは、OAS2DOTTYPE
の処理が後述の別の場所に移されたためです。
- 以前は
OSWITCH
の特殊処理の削除:- 以前は
OSWITCH
ノード(一般的なswitch
文)がOTYPESW
(type switch)である場合に、インストゥルメンテーションをスキップするreturn;
がありました。このコミットでは、このOSWITCH
の特殊な処理が完全に削除されました。これにより、type switch
がインストゥルメンテーションの対象となる道が開かれました。
- 以前は
+ case OTYPESW:
の追加:- 新たに
OTYPESW
ノードが明示的にracewalknode
のcase
として追加されました。 racewalknode(&n->right, init, 0, 0);
は、type switch
の右辺(通常はインターフェース変数)に対して再帰的にracewalknode
を呼び出し、その中のメモリアクセスもインストゥルメンテーションの対象とします。
- 新たに
+ case OAPPEND:
の追加:- 以前は
OAPPEND
ノード(append
関数呼び出し)は「// unimplemented」とコメントされ、goto ret;
によってインストゥルメンテーションがスキップされていました。 - このコミットでは、
OAPPEND
がOMAKESLICE
,OCALL
,OCOPY
などと同様に、通常のインストゥルメンテーション対象のノードリストに追加されました。これにより、append
が内部的に行うメモリ操作がRace Detectorによって監視されるようになります。
- 以前は
+ case OAS2DOTTYPE:
の再追加:OAS2DOTTYPE
がODOTTYPE
,ODOTTYPE2
などと共に、別のインストゥルメンテーション対象ノードのグループに再追加されました。これは、type switch
のv := i.(type)
のような構文が内部的にOAS2DOTTYPE
に変換される場合があり、その代入操作も監視する必要があるためです。
+ case OSWITCH:
の再追加:OSWITCH
がOIF
,OCALLMETH
,ORETURN
などと共に、別のインストゥルメンテーション対象ノードのグループに再追加されました。これにより、一般的なswitch
文も適切にウォークされ、その中のメモリアクセスが監視されるようになります。
- case OAPPEND:
の削除:- 以前の「// unimplemented」とコメントされていた
OAPPEND
のスキップロジックが削除されました。
- 以前の「// unimplemented」とコメントされていた
-
callinstr
関数の変更:- || b->type->etype == TARRAY
を+ || b->op == OINDEX
に変更:callinstr
関数は、特定のノードに対してインストゥルメンテーションが必要かどうかを判断します。- 以前は
b
が配列型(TARRAY
)であるかどうかをチェックしていましたが、これをb
がOINDEX
(配列/スライスインデックスアクセス)操作であるかどうかをチェックするように変更しました。これは、配列型そのものよりも、実際に配列へのアクセスが行われる操作を直接監視する方が、より正確なインストゥルメンテーションにつながるためです。
-
basenod
関数の変更:- if(n->op == OINDEX) {
を+ if(n->op == OINDEX && isfixedarray(n->type)) {
に変更:basenod
関数は、与えられたノードが指すメモリ領域の「基底」となるノードを特定します。- 以前は
OINDEX
(配列/スライスインデックスアクセス)の場合に無条件にn->left
(基底配列/スライス)を返していました。 - この変更により、
OINDEX
が固定長配列(isfixedarray(n->type)
)へのアクセスである場合にのみn->left
を返すようになりました。スライスは動的なサイズを持つため、その基底配列は実行時に変更される可能性があり、固定長配列とは異なる扱いが必要です。この修正により、Race Detectorがスライス操作におけるメモリの基底をより正確に追跡できるようになります。
これらの変更は、Goコンパイラが生成するRace Detectorのインストゥルメンテーションコードの精度と網羅性を大幅に向上させ、append
や type switch
といったGo言語の重要な構文におけるデータ競合の検出能力を高めます。
関連リンク
- Go Issue #4228: https://github.com/golang/go/issues/4228
- Gerrit Change-Id: https://golang.org/cl/8041043
参考にした情報源リンク
- Go言語公式ドキュメント - The Go Race Detector: https://go.dev/doc/articles/race_detector
- Go言語公式ドキュメント - Slices: usage and internals: https://go.dev/blog/slices
- Go言語公式ドキュメント - The Go Programming Language Specification - Type switches: https://go.dev/ref/spec#Type_switches
- Goコンパイラの内部構造に関する一般的な情報源:
- Goのソースコード(
src/cmd/gc/
ディレクトリ) - Goのコンパイラに関するブログ記事やプレゼンテーション(例: "Go's Compiler: The Details" by Russ Cox)
- GoのASTノードに関する情報(Goのソースコード内の
src/cmd/compile/internal/syntax/nodes.go
やsrc/cmd/compile/internal/ir/ir.go
など)
- Goのソースコード(