[インデックス 19454] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)における競合状態のコンパイルエラー「non-orig name」を修正するものです。特に、sinit.c
ファイル内のstaticcopy
関数におけるNode*
の扱いに関する問題に対処しています。
コミット
commit 8a2db409c4e08ec9a8d87bdcaea928083f6293fc
Author: Russ Cox <rsc@golang.org>
Date: Tue May 27 23:59:27 2014 -0400
cmd/gc: fix race compilation failure 'non-orig name'
CL 51010045 fixed the first one of these:
cmd/gc: return canonical Node* from temp
For historical reasons, temp was returning a copy
of the created Node*, not the original Node*.
This meant that if analysis recorded information in the
returned node (for example, n->addrtaken = 1), the
analysis would not show up on the original Node*, the
one kept in fn->dcl and consulted during liveness
bitmap creation.
Correct this, and watch for it when setting addrtaken.
Fixes #7083.
R=khr, dave, minux.ma
CC=golang-codereviews
https://golang.org/cl/51010045
CL 53200043 fixed the second:
cmd/gc: fix race build
Missed this case in CL 51010045.
TBR=khr
CC=golang-codereviews
https://golang.org/cl/53200043
This CL fixes the third. There are only three nod(OXXX, ...)
calls in sinit.c, so maybe we're done. Embarassing that it
took three CLs to find all three.
Fixes #8028.
LGTM=khr
R=golang-codereviews, khr
CC=golang-codereviews, iant
https://golang.org/cl/100800046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8a2db409c4e08ec9a8d87bdcaea928083f6293fc
元コミット内容
このコミットは、Goコンパイラ(cmd/gc
)が競合検出モード(-race
フラグ)でコンパイルする際に発生する「non-orig name」というコンパイルエラーを修正します。このエラーは、コンパイラが内部的に生成するノード(Node*
)の「オリジナル」ではないコピーを扱ってしまうことに起因していました。
具体的には、sinit.c
ファイル内のnod(OXXX, ...)
という形式のノード生成呼び出しが問題を引き起こしていました。このコミットは、同様の問題を修正した過去の2つの変更(CL 51010045とCL 53200043)に続く、3番目の修正となります。コミットメッセージでは、sinit.c
にはこのような呼び出しが3つしかないため、これで全ての問題が解決された可能性に言及しつつ、3つのCLに分けて修正されたことへの「恥ずかしさ」を表明しています。
この修正は、Issue #8028を解決します。
変更の背景
Goコンパイラには、データ競合を検出するための「レース検出器(Race Detector)」が組み込まれています。この機能は、プログラムの実行時にメモリへの同時アクセスを監視し、潜在的な競合状態を報告することで、並行プログラミングにおけるバグの特定を支援します。レース検出器は、コンパイル時に特定のインストゥルメンテーションコードを挿入することで機能します。
このコミットの背景にある問題は、コンパイラの内部表現、特に抽象構文木(AST)のノード(Node*
)の扱いにありました。Goコンパイラは、ソースコードを解析してASTを構築し、そのASTに対して様々な最適化や解析を行います。レース検出器もこの解析フェーズで、変数のアドレスが取られているかどうか(addrtaken
フラグ)などの情報をノードに記録します。
しかし、過去のコンパイラの設計では、temp
関数(一時変数を生成する関数)などが、作成されたNode*
の「コピー」を返していました。この「コピー」は、元のノードとは異なるメモリ上のエンティティでありながら、論理的には同じ変数を指していました。このため、解析フェーズでコピーされたノードにaddrtaken = 1
のような情報が記録されても、コンパイラが後で参照する「元のノード」(例えば、関数の宣言リストfn->dcl
に保持されているノードや、ライブネスビットマップの生成時に参照されるノード)には、その情報が反映されませんでした。
この不整合が、レース検出器が期待する情報と実際のノードの状態との間にギャップを生じさせ、「non-orig name」というコンパイルエラーを引き起こしていました。これは、レース検出器が、本来アドレスが取られているとマークされているべき変数について、その情報が欠落しているために、不正な状態を検出したことを示唆しています。
このコミットは、同様の問題を修正した以下の2つの先行コミットに続くものです。
- CL 51010045 (
cmd/gc: return canonical Node* from temp
):temp
関数が、作成されたNode*
のコピーではなく、正規のNode*
を返すように修正しました。これにより、addrtaken
などの解析情報が元のノードに正しく反映されるようになりました。 - CL 53200043 (
cmd/gc: fix race build
): CL 51010045で修正しきれなかった別のケースを修正しました。
本コミットは、sinit.c
内の残りの同様の問題を解決し、この種の「non-orig name」エラーを完全に解消することを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラの内部構造と概念に関する知識が必要です。
cmd/gc
: Go言語の公式コンパイラの一部であり、Goソースコードを機械語に変換する主要なコンポーネントです。Goコンパイラは、フロントエンド(字句解析、構文解析、AST構築)、ミドルエンド(型チェック、最適化、エスケープ解析など)、バックエンド(コード生成)から構成されます。- 抽象構文木(AST)と
Node*
: コンパイラはソースコードを解析し、プログラムの構造を木構造で表現した抽象構文木(AST)を構築します。ASTの各要素は「ノード」と呼ばれ、Goコンパイラ内部ではNode
構造体(またはそのポインタNode*
)で表現されます。変数、定数、式、文、関数宣言など、プログラムのあらゆる要素がノードとして表現されます。 Node->orig
フィールド:Node
構造体には、orig
というフィールドが存在します。このフィールドは、ノードが元のソースコードのどの部分に対応するか、あるいはノードがコピーや変換によって生成された場合に、その「オリジナル」のノードを指し示すために使用されます。コンパイラの解析フェーズでは、このorig
フィールドを介して、論理的に同じエンティティ(例えば、同じ変数)に関連するすべてのノードが、一貫した情報(例:addrtaken
フラグ)を持つことが重要になります。addrtaken
フラグ:Node
構造体には、addrtaken
というフラグ(通常はビットフィールド)があります。このフラグは、そのノードが表す変数のアドレスがプログラム内で取得されたかどうかを示します。アドレスが取られた変数は、ポインタを介してアクセスされる可能性があるため、レース検出器やエスケープ解析(変数がヒープに割り当てられるべきかスタックに割り当てられるべきかを決定する解析)にとって重要な情報となります。- レース検出器(Race Detector): Go言語に組み込まれている強力なツールで、並行プログラムにおけるデータ競合を検出します。コンパイル時に
-race
フラグを付けてビルドすると、コンパイラはメモリアクセスを監視する追加のコードを生成します。この監視コードは、実行時に複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも一方が書き込み操作である場合に警告を発します。レース検出器が正しく機能するためには、コンパイラが変数の特性(例:addrtaken
)を正確に把握している必要があります。 sinit.c
: Goコンパイラのソースコードの一部で、主に静的初期化(static initialization)に関連する処理を扱うファイルです。グローバル変数やパッケージレベルの変数の初期化、特に複雑な初期化式を持つ場合の処理が含まれます。このファイル内で、コンパイラは一時的なノードを生成したり、既存のノードをコピーしたりする操作を行うことがあります。nod(OXXX, ...)
:cmd/gc
におけるノード生成関数の一般的なパターンです。nod
は「node」の略で、OXXX
はノードのオペレーションタイプ(例:ONAME
は変数名、OADD
は加算など)を示します。この関数は、指定されたオペレーションタイプとオペランドを持つ新しいASTノードを作成します。
技術的詳細
このコミットが修正する問題は、Goコンパイラの内部でASTノードがコピーされる際に、そのコピーが「オリジナル」のノードとの関連性を失ってしまうことにありました。特に、sinit.c
内のstaticcopy
関数が関与していました。
staticcopy
関数は、静的初期化のコンテキストでノードをコピーする際に使用されます。元のコードでは、nod(OXXX, N, N)
を呼び出して新しいノードll
を作成し、その後*ll = n1;
という代入によって、既存のノードn1
の内容をll
にコピーしていました。
この*ll = n1;
という操作は、n1
の構造体メンバーをll
にビット単位でコピーします。これには、n1
のorig
フィールドも含まれます。しかし、n1
自体が既にコピーされたノードである場合、そのorig
フィールドはさらに別の「オリジナル」を指している可能性があります。問題は、ll
がn1
の「コピー」として作成されたにもかかわらず、ll
自身のorig
フィールドがll
自身を指すように設定されていなかった点にありました。
レース検出器などのコンパイラ解析は、ノードのorig
フィールドを辿って、そのノードが表す論理的なエンティティ(変数など)の「真のオリジナル」に到達し、そこに記録されたaddrtaken
などの情報を参照します。もしll
がn1
のコピーであり、n1
がさらに別のノードのコピーである場合、ll
のorig
フィールドがn1
のorig
フィールドを指していると、解析は正しい「オリジナル」に到達できないか、あるいは誤ったノードに情報を記録してしまう可能性がありました。
このコミットの修正は、ll->orig = ll;
という一行を追加することで、この問題を解決しています。この行は、ll
がn1
の内容をコピーされた後、ll
自身のorig
フィールドをll
自身に設定し直します。これにより、ll
は「完全に独立したコピー」として扱われるようになります。つまり、ll
はn1
の構造を模倣しつつも、そのorig
フィールドはll
自身を指すため、ll
に対する解析結果(例: addrtaken
)は、ll
自身に直接関連付けられ、他のノードのorig
チェーンに影響を与えることなく、独立して処理されるようになります。
この変更により、レース検出器がll
のような一時的に生成されたノードを解析する際に、そのノードが表す変数のaddrtaken
情報などを正しく追跡できるようになり、「non-orig name」エラーが解消されます。
コアとなるコードの変更箇所
変更は以下の2つのファイルで行われています。
-
src/cmd/gc/sinit.c
:--- a/src/cmd/gc/sinit.c +++ b/src/cmd/gc/sinit.c @@ -354,6 +354,7 @@ staticcopy(Node *l, Node *r, NodeList **out) else { ll = nod(OXXX, N, N); *ll = n1; + ll->orig = ll; // completely separate copy if(!staticassign(ll, e->expr, out)) { // Requires computation, but we're // copying someone else's computation.
staticcopy
関数内で、ll = nod(OXXX, N, N);
と*ll = n1;
の後に、ll->orig = ll;
という行が追加されています。 -
test/fixedbugs/issue8028.go
:--- /dev/null +++ b/test/fixedbugs/issue8028.go @@ -0,0 +1,27 @@ +// compile + +// 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 8028. Used to fail in -race mode with "non-orig name" error. + +package p + +var ( + t2 = T{F, "s1"} + t1 = T{F, "s2"} + + tt = [...]T{t1, t2} +) + +type I interface{} + +type T struct { + F func() I + S string +} + +type E struct{} + +func F() I { return new(E) }
Issue #8028を再現し、修正が正しく適用されたことを確認するための新しいテストファイルが追加されています。このテストは、
-race
モードでコンパイルした際に「non-orig name」エラーが発生しないことを検証します。
コアとなるコードの解説
src/cmd/gc/sinit.c
の変更点であるll->orig = ll;
は、このコミットの核心です。
staticcopy
関数は、静的初期化のコンテキストで、あるノードn1
の内容を新しいノードll
にコピーする役割を担っています。
元のコードでは、以下の手順でノードが作成・コピーされていました。
ll = nod(OXXX, N, N);
: 新しい空のノードll
が作成されます。この時点では、ll->orig
は通常nil
またはデフォルト値です。*ll = n1;
:n1
の全てのフィールドがll
にコピーされます。これにはn1->orig
も含まれます。もしn1
が既に別のノードのコピーであった場合、ll->orig
はn1->orig
と同じ値を持ち、ll
自身を指しません。
この挙動が問題でした。コンパイラの解析フェーズ、特にレース検出器は、ノードのorig
フィールドを辿って、そのノードが表す論理的なエンティティ(例えば、変数)の「真のオリジナル」に到達しようとします。もしll
がn1
のコピーであり、n1
がさらに別のノードのコピーである場合、ll
のorig
フィールドがn1
のorig
フィールドを指していると、解析は正しい「オリジナル」に到達できないか、あるいは誤ったノードに情報を記録してしまう可能性がありました。これにより、addrtaken
のような重要なフラグが正しく伝播せず、レース検出器が誤った状態を報告し、「non-orig name」エラーが発生していました。
追加されたll->orig = ll;
という行は、*ll = n1;
によるコピーの後で、明示的にll
のorig
フィールドをll
自身に設定し直します。この操作により、ll
はn1
の構造(型、オペレーションなど)を継承しつつも、その「オリジナル」としてはll
自身が設定されます。コミットメッセージにある「completely separate copy」というコメントが示す通り、ll
はn1
の論理的なコピーではなく、n1
の構造を持つ新しい独立したノードとして扱われるようになります。
これにより、ll
に対して行われるaddrtaken
などの解析結果は、ll
自身に正しく関連付けられ、他のノードのorig
チェーンに影響を与えることなく、独立して処理されるようになります。結果として、レース検出器が期待する情報と実際のノードの状態との間の不整合が解消され、「non-orig name」エラーが修正されます。
test/fixedbugs/issue8028.go
は、この修正を検証するためのテストケースです。このテストは、静的初期化される構造体配列tt
を定義しており、その要素t1
とt2
が関数ポインタF
と文字列を持つT
型の構造体です。F
関数はnew(E)
を返すため、ヒープ割り当てが発生し、レース検出器が監視する対象となります。この種のコードパターンが、修正前のコンパイラでは「non-orig name」エラーを引き起こしていました。このテストがエラーなくコンパイルできることで、修正が正しく機能していることが確認されます。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/8a2db409c4e08ec9a8d87bdcaea928083f6293fc
- Go Issue #8028: https://github.com/golang/go/issues/8028
- Go CL 51010045: https://golang.org/cl/51010045
- Go CL 53200043: https://golang.org/cl/53200043
- Go CL 100800046 (本コミットに対応するCL): https://golang.org/cl/100800046
参考にした情報源リンク
- Go言語の公式ドキュメントおよびソースコード
- Goコンパイラの内部構造に関する一般的な知識
- データ競合検出器に関する一般的な情報
- Go言語のIssueトラッカーおよびコードレビューシステム (Gerrit)