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

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

このコミットは、Goコンパイラ(cmd/gc)における競合状態(race condition)のビルドに関する修正です。具体的には、以前の変更(CL 51010045)で見落とされていたケースに対応し、コンパイラの静的コピー処理におけるNodeorigフィールドの扱いを修正しています。

コミット

commit fbfb9430dc687d399ded22a38abab035e3fa462b
Author: Russ Cox <rsc@golang.org>
Date:   Thu Jan 16 10:11:06 2014 -0500

    cmd/gc: fix race build
    
    Missed this case in CL 51010045.
    
    TBR=khr
    CC=golang-codereviews
    https://golang.org/cl/53200043

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

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

元コミット内容

cmd/gc: fix race build Missed this case in CL 51010045.

このコミットメッセージは非常に簡潔ですが、以下の重要な情報を含んでいます。

  • cmd/gc: fix race build: Goコンパイラ(gc)における「競合状態ビルド」の修正であることを示しています。これは、Goの競合検出器(Race Detector)が有効な状態でビルドする際に問題が発生していたことを示唆しています。
  • Missed this case in CL 51010045.: 以前の変更リスト(Change List、Gerritにおけるコミットの単位)であるCL 51010045で、特定のケースが見落とされていたことを明示しています。このCLは、おそらく競合検出器に関連する変更か、コンパイラのコード生成に関する変更であったと推測されます。

変更の背景

このコミットの背景には、GoのコンパイラがGoの競合検出器(Race Detector)と連携して動作する際の特定のバグが存在したことが挙げられます。Goの競合検出器は、並行処理におけるデータ競合を検出するための強力なツールであり、Goプログラムのビルド時に特殊なインストゥルメンテーション(計測コードの挿入)が行われます。

CL 51010045は、おそらくこの競合検出器のサポートに関連する変更、またはコンパイラの内部的なノード(抽象構文木の一部)のコピー処理に関する変更を導入したと考えられます。しかし、その変更において、特定のコードパスやデータ構造のコピー方法が不適切であったため、競合検出器を有効にしてビルドした際に、誤った動作やクラッシュを引き起こす可能性のある競合状態が発生していました。

このコミットは、その見落とされたケースを特定し、コンパイラの内部表現であるNode構造体のコピー処理を修正することで、競合検出器が有効な状態でのビルドが正しく行われるようにすることを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念に関する知識が役立ちます。

  1. Goコンパイラ (cmd/gc): Go言語の公式コンパイラです。ソースコードを解析し、抽象構文木(AST)を構築し、最適化を行い、最終的に実行可能なバイナリを生成します。src/cmd/gcディレクトリにそのソースコードがあります。
  2. 抽象構文木 (AST): プログラムのソースコードを木構造で表現したものです。コンパイラはソースコードを直接操作するのではなく、ASTを介してプログラムの構造と意味を理解し、変換します。Goコンパイラでは、ASTの各ノードがNode構造体で表現されます。
  3. Go Race Detector: Go 1.1から導入された、並行処理におけるデータ競合を検出するためのツールです。go run -racego build -raceのように-raceフラグを付けてビルド・実行することで有効になります。競合検出器は、メモリへのアクセスを監視し、複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも一方が書き込み操作である場合に警告を発します。これを実現するために、コンパイラは特別なインストゥルメンテーションコードを生成します。
  4. Node構造体とorigフィールド: Goコンパイラの内部では、ASTの各要素がNode構造体として表現されます。このNode構造体には、そのノードがソースコードのどの部分に由来するかを示す情報や、型情報、オフセットなどが含まれます。origフィールドは、特に最適化や変換の過程でノードがコピーされたり変換されたりする際に、そのノードが元々どのノードから派生したか(オリジナルのノードは何か)を追跡するために使用されることがあります。これは、デバッグ情報やエラー報告の際に元のソースコードの位置を正確に指し示すために重要です。
  5. 静的コピー (staticcopy): コンパイラの最適化フェーズやコード生成フェーズにおいて、特定のノードやサブツリーをコピーする必要がある場合があります。staticcopy関数は、このような静的なコピー操作を行うための内部関数であると推測されます。この関数は、元のノードの情報を保持しつつ、新しい独立したノードを作成する役割を担います。

技術的詳細

このコミットの技術的な核心は、Goコンパイラのsrc/cmd/gc/sinit.cファイル内のstaticcopy関数におけるNode構造体のコピー処理にあります。

staticcopy関数は、おそらくコンパイラがASTノードを複製する際に使用されます。元のコードでは、rr = nod(OXXX, N, N); *rr = *orig;という行で、新しいノードrrを作成し、元のノードorigの内容をコピーしています。しかし、このコピーはシャローコピー(浅いコピー)であり、origフィールドのようなポインタや参照型のフィールドは、元のノードと同じメモリ位置を指したままになります。

問題は、Goの競合検出器が有効なビルドにおいて、コンパイラがASTを変換する際に、これらのノードのorigフィールドが正しく独立した状態になっていないと、競合検出器が誤った情報を解釈したり、コンパイラ自身の内部状態が不正になったりする可能性があったことです。特に、競合検出器が挿入するインストゥルメンテーションコードが、ノードのorigフィールドを通じて元のノードの情報を参照する際に、予期せぬ競合状態を引き起こすことが考えられます。

このコミットで追加された行 rr->orig = rr; // completely separate copy は、この問題を解決します。

  • rr->orig = rr;: 新しくコピーされたノードrrorigフィールドを、そのノード自身(rr)を指すように設定しています。これにより、rrは完全に独立したコピーとなり、元のノードorigとの参照関係が断ち切られます。
  • // completely separate copy: このコメントは、この変更の意図が、ノードを「完全に独立したコピー」にすることであることを明確に示しています。

この修正により、競合検出器が有効なビルドプロセス中に、コンパイラがノードのorigフィールドを介して誤った共有状態を検出したり、内部的な不整合を引き起こしたりする可能性が排除されます。

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

変更はsrc/cmd/gc/sinit.cファイルの一箇所のみです。

--- a/src/cmd/gc/sinit.c
+++ b/src/cmd/gc/sinit.c
@@ -359,6 +359,7 @@ staticcopy(Node *l, Node *r, NodeList **out)\n 					// copying someone else\'s computation.\n 					rr = nod(OXXX, N, N);\n 					*rr = *orig;\n+\t\t\t\t\trr->orig = rr; // completely separate copy\n \t\t\t\t\trr->type = ll->type;\n \t\t\t\t\trr->xoffset += e->xoffset;\n \t\t\t\t\t*out = list(*out, nod(OAS, ll, rr));\n```

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

追加された行は以下の通りです。

```c
rr->orig = rr; // completely separate copy

この行は、staticcopy関数内で新しいNodeであるrrが作成され、元のorigノードの内容がコピーされた直後に実行されます。

  • rr: staticcopy関数によって新しく作成されたNode構造体へのポインタです。これは、元のASTノードのコピーとして機能します。
  • orig: コピー元のオリジナルのNode構造体へのポインタです。
  • *rr = *orig;: この行は、origが指すNode構造体の内容を、rrが指す新しいNode構造体にコピーします。しかし、これは構造体のメンバーごとのコピーであり、もしNode構造体内にポインタ型のフィールド(例えばorig自身)があれば、そのポインタの値(アドレス)がコピーされるだけで、指している先のデータは共有されたままになります。
  • rr->orig = rr;: この追加された行は、新しくコピーされたノードrrorigフィールドを、rr自身のアドレスに設定し直します。これにより、rrorigフィールドは、元のノードのorigフィールドが指していたものとは関係なく、rr自身を「オリジナル」として参照するようになります。

この変更の目的は、staticcopyによって生成されたノードが、そのorigフィールドを通じて元のノードの複雑な参照グラフに依存しないようにすることです。特に、Goの競合検出器が有効なビルドでは、コンパイラが生成するコードに特別なインストゥルメンテーションが追加されます。このインストゥルメンテーションがノードのorigフィールドを追跡する際に、シャローコピーによって生じる共有状態が問題を引き起こす可能性があったため、この修正が必要となりました。

結果として、rrは完全に独立したノードとして扱われるようになり、競合検出器が正しく機能し、コンパイラが安定して動作するようになります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Goコンパイラのソースコード(src/cmd/gc
  • Go Race Detectorに関するブログ記事やドキュメント
  • Gerrit Code ReviewシステムにおけるGoプロジェクトの変更履歴
  • Goコンパイラの内部構造に関する一般的な知識(AST、ノード表現など)
  • Goのコミットメッセージの慣習に関する情報