[インデックス 14204] ファイルの概要
このコミットは、Goコンパイラの抽象構文木(AST)におけるOAS2RECV
ノードの厳密なツリープロパティの修正に関するものです。具体的には、typecheck
およびwalk
フェーズにおいて、OAS2RECV
からOAS2
およびOSELRECV2
への変換時に、ASTのノードが誤って複製され、ASTの厳密なツリー構造が破壊される問題に対処しています。この問題は、エスケープ解析(esc.c
)やインライン化(inl.c
)など、ASTのツリー構造に依存するコンパイラパスに影響を与えていました。
コミット
- Author: Luuk van Dijk lvd@golang.org
- Date: Mon Oct 22 10:01:14 2012 +0200
- Commit Hash: e7f89fcb1cdd5fc41377108fcaad2363d4456b24
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e7f89fcb1cdd5fc41377108fcaad2363d4456b24
元コミット内容
cmd/gc: fix strict tree property for AST for OAS2RECV nodes.
in typecheck and walk, conversion from OAS2RECV to OAS2
and to OSELRECV2 duplicated the ->rlist->n to ->right
thereby destroying the strict tree-ness of the AST (up to
ONAMES) of course. Several recursions in esc.c and inl.c
and probably elsewhere assume nodes of the tree aren't duplicated.
rather than defensively code around this, i'd rather assert
these cases away and fix their cause.
(this was tripped in 6741044)
R=rsc
CC=golang-dev
https://golang.org/cl/6750043
変更の背景
このコミットは、Goコンパイラの内部的な問題、特にASTの構造の整合性に関するバグを修正するために行われました。コミットメッセージによると、OAS2RECV
ノード(チャネルからの2値受信代入、例: x, ok = <-c
)がtypecheck
およびwalk
フェーズでOAS2
やOSELRECV2
に変換される際に、ノードのポインタが誤って複製されていました。具体的には、->rlist->n
が->right
に複製されることで、ASTの「厳密なツリー性(strict tree-ness)」が損なわれていました。
ASTが厳密なツリー構造を持つことは、コンパイラの様々な最適化パスや解析パス(例: エスケープ解析 esc.c
、インライン化 inl.c
)が正しく機能するための前提条件です。ノードの複製は、これらのパスが同じノードを複数回処理したり、予期しない状態に遭遇したりする原因となり、コンパイラの誤動作やクラッシュにつながる可能性があります。コミットメッセージでは、この問題が以前のコミット(6741044
)でトリガーされたと述べられており、その根本原因を修正する必要がありました。
前提知識の解説
GoコンパイラのAST (Abstract Syntax Tree)
Goコンパイラは、ソースコードを解析する際に、その構造を抽象構文木(AST)として表現します。ASTは、プログラムの構造を木構造で表現したもので、各ノードはプログラムの要素(変数、関数呼び出し、演算子など)に対応します。コンパイラは、このASTを様々なフェーズ(型チェック、最適化、コード生成など)で処理していきます。
OAS2RECV
、ORECV
、OSELRECV2
これらはGoコンパイラの内部的な操作コード(Op)であり、ASTノードの種類を示します。
ORECV
: 単純なチャネル受信操作(例:<-c
)を表します。OAS2RECV
: チャネルからの2値受信代入(例:x, ok = <-c
)を表します。これは、受信値と成功/失敗を示すブール値の両方を受け取るパターンです。OAS2
: 一般的な2値代入操作を表します。OSELRECV2
:select
ステートメント内で使用される、チャネルからの2値受信操作を表します。select
ブロック内のcase
文でx, ok = <-c
のような形式がこれに該当します。
ASTの「厳密なツリー性(strict tree-ness)」
ASTが「厳密なツリー性」を持つとは、各ノードがAST内で一意であり、複数の親ノードから参照されることがない状態を指します。つまり、ASTはグラフではなく、純粋な木構造であるべきだという原則です。この原則が破られると、コンパイラの内部処理でノードが重複して処理されたり、状態管理が複雑になったり、予期せぬ副作用が発生したりする可能性があります。
typecheck
とwalk
フェーズ
Goコンパイラの主要なフェーズの一部です。
typecheck
: ASTの各ノードに対して型チェックを行い、型の整合性を検証します。このフェーズで、一部のASTノードはより具体的な操作コードに変換されることがあります。walk
: ASTを走査し、様々な変換や最適化を行います。例えば、一部のASTノードはより低レベルの表現に変換されたり、最適化のために構造が変更されたりします。
esc.c
とinl.c
これらはGoコンパイラのソースファイルの一部であり、それぞれ以下の機能に関連しています。
esc.c
: エスケープ解析(Escape Analysis)を担当します。変数がヒープにエスケープするかどうかを判断し、スタックに割り当てられるべき変数を特定することで、ガベージコレクションの負荷を軽減します。エスケープ解析は、変数の参照関係をAST上で追跡するため、ASTの厳密なツリー構造に強く依存します。inl.c
: インライン化(Inlining)を担当します。小さな関数呼び出しを呼び出し元に直接展開することで、関数呼び出しのオーバーヘッドを削減し、パフォーマンスを向上させます。インライン化もASTの構造を操作するため、ASTの整合性が重要です。
技術的詳細
このコミットが修正している問題は、OAS2RECV
ノードがtypecheck
およびwalk
フェーズで処理される際の、ASTノードの不適切なポインタ操作に起因します。
元のコードでは、x, ok = <-c
のようなOAS2RECV
ノードが、typecheck
フェーズでOSELRECV2
(select
文内での2値受信)やOAS2
(一般的な2値代入)に変換される際に、n->rlist->n
(受信操作の右辺のノード)がn->right
に直接代入されていました。
// 変更前 (typecheck.c)
case ORECV:
n->op = OAS2RECV;
n->right = n->rlist->n; // ここでノードが複製される問題
goto common;
そして、select.c
のtypecheckselect
関数内でも同様のパターンが見られました。
// 変更前 (select.c)
case OAS2RECV:
// ...
n->right = n->rlist->n; // ここでもノードが複製される問題
break;
このn->right = n->rlist->n;
という代入は、n->rlist->n
が指す既存のノードをn->right
にも指させることになります。これにより、同じASTノードが複数の親ノード(この場合はn->rlist
とn
)から参照される状態が発生し、ASTの「厳密なツリー性」が破壊されます。
ASTの厳密なツリー性が破壊されると、esc.c
やinl.c
のような、ASTを再帰的に走査して処理を行うコンパイラパスが問題に直面します。これらのパスは、各ノードが一度だけ処理されること、またはツリー構造が予測可能であることを前提としています。ノードが複製されていると、例えばエスケープ解析が同じ変数を複数回処理して誤った結果を出したり、インライン化が不正なASTを生成したりする可能性があります。
コミットメッセージにある「rather than defensively code around this, i'd rather assert these cases away and fix their cause.」という記述は、この問題に対して、各コンパイラパスで防御的なコードを追加して対処するのではなく、根本原因であるASTのツリー構造の破壊を修正するという方針を示しています。
コアとなるコードの変更箇所
このコミットでは、主にsrc/cmd/gc/select.c
とsrc/cmd/gc/typecheck.c
の2つのファイルが変更されています。
src/cmd/gc/select.c
--- a/src/cmd/gc/select.c
+++ b/src/cmd/gc/select.c
@@ -62,7 +62,7 @@ typecheckselect(Node *sel)
case OAS2RECV:
// convert x, ok = <-c into OSELRECV2(x, <-c) with ntest=ok
- if(n->right->op != ORECV) {
+ if(n->rlist->n->op != ORECV) {
yyerror("select assignment must have receive on right hand side");
break;
}
@@ -70,6 +70,7 @@ typecheckselect(Node *sel)
n->left = n->list->n;
n->ntest = n->list->next->n;
n->right = n->rlist->n;
+ n->rlist = nil;
break;
case ORECV:
@@ -146,7 +147,7 @@ walkselect(Node *sel)
a = nod(OAS2, N, N);
a->list = n->list;
- a->rlist = n->rlist;
+ a->rlist = list1(n->right);
n = a;
typecheck(&n, Etop);
break;
src/cmd/gc/typecheck.c
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -2574,7 +2574,6 @@ typecheckas2(Node *n)
goto common;
case ORECV:
n->op = OAS2RECV;
- n->right = n->rlist->n;
goto common;
case ODOTTYPE:
n->op = OAS2DOTTYPE;
コアとなるコードの解説
src/cmd/gc/select.c
の変更
-
typecheckselect
関数内の変更:if(n->right->op != ORECV)
からif(n->rlist->n->op != ORECV)
への変更: これは、OAS2RECV
ノードの右辺がORECV
(受信操作)であることを確認するチェックです。変更前はn->right
を見ていましたが、n->right
は変換後に設定されるため、変換前の元の受信ノードはn->rlist->n
にあります。この修正により、チェックがより正確なASTノードに対して行われるようになります。n->rlist = nil;
の追加:n->right = n->rlist->n;
によってn->rlist->n
がn->right
に「移動」された後、元のn->rlist
は不要になります。この行を追加することで、n->rlist
をnil
に設定し、古い参照をクリアすることで、ASTのツリー構造の整合性を保ちます。これにより、同じノードが複数のパスから参照されることを防ぎます。
-
walkselect
関数内の変更:a->rlist = n->rlist;
からa->rlist = list1(n->right);
への変更:walkselect
関数は、select
文のcase
内のOAS2RECV
ノードをOAS2
ノードに変換する処理を行います。変更前は、新しいOAS2
ノードのrlist
に古いOAS2RECV
ノードのrlist
を直接代入していました。しかし、typecheckselect
でn->rlist
がnil
に設定されるため、この代入は正しくありません。 新しいコードでは、a->rlist = list1(n->right);
としています。これは、n->right
が既に変換後の受信ノードを指しているため、そのノードを新しいOAS2
ノードのrlist
のリストとして設定することで、ASTの整合性を維持します。list1
は単一のノードからなるリストを作成するヘルパー関数です。
src/cmd/gc/typecheck.c
の変更
n->right = n->rlist->n;
の削除:typecheckas2
関数は、2値代入(OAS2
)の型チェックを行う関数です。ORECV
(受信操作)が右辺にある場合にOAS2RECV
に変換するロジックが含まれています。変更前は、ここでn->right = n->rlist->n;
という行があり、これがASTノードの複製を引き起こしていました。この行を削除することで、OAS2RECV
ノードが作成される際に、n->rlist->n
がn->right
に重複して参照されることを防ぎ、ASTの厳密なツリー性を維持します。この修正により、OAS2RECV
ノードの構造がよりクリーンになり、後続のコンパイラパスでの問題が回避されます。
これらの変更は、GoコンパイラのAST内部表現の整合性を保つために重要であり、特にエスケープ解析やインライン化といった、ASTの正確な構造に依存する最適化パスの信頼性を向上させます。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/e7f89fcb1cdd5fc41377108fcaad2363d4456b24
- Go Code Review: https://golang.org/cl/6750043
参考にした情報源リンク
- Go compiler AST OAS2RECVに関するWeb検索結果:
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF15s6_yIZuOAikXMSqSk1HqAX7-VYE6E_zykR0GBLCNgTPAVDLMaASBSH_UPj8DZLprKFZPhkc7LC4UpESoZ7O2oqFt5vD0qH2o3ePAPV8llzxUBNjRgRQgpZoHG1UIXAlw6VsPWWEE8Lpp2I-XDiI7dlQRH2aNB9cpqMmWMYeRPcR
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEoNnBON6VezXcfSOdIPSQB9lUzV9IdK-h9Lw5nkp73VJtcK8HmEW5SmNb3ix-Wnj4sQY5V9ycbVvDiCt3KwDKKHq-9Tar6BzcQphMfKHmip0vFET3QCvRnQBH7LzdA9Fc4EQZM3JrzGNA0sXcPLRnA6A3H9-xFXfXPvpU1orQG4LV3NgSlVvc=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFH7Wbv8cR3-yzhE-WZkrplzJEecIcaPYumj2bauUVYbQwJQxuYjhv26Dn9r6M5gBvKao-4eS0Uf9pGzjG_xv7P_PSJNRn77Yc9WFlspvEiPtD9TXPoY05N7id3OcoapOCXMvJnOd43j-yjbr0aTi7L56aIgLUfRZXcl-SAEVnkS9VoKqepCuoCK0iD4JgGtfeSKUOuZIIKQCFl7iQvXqmG9Z_ybqM3vN063nvQ1dhw26eib1JbGM--7r5W9mJgA=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEjqNDmdPFql1gGFSFD0PORC7qyFpz1cwB2y2QrkaeyjVB2xz_V9rX6fk2fu8UxcVmUPOwUd4NwOvWLcP9KFxh8_IIL3wuZir_n7Z8Drleu66c=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGFDaKP8OD41IpasRMwbYYYc2ugl3FaUXx76a5aPxHXIKM2-9gFn07Lv7znGOnN1I8jEixilxT1LVtEeR4vNdtzGVi4zx6uHfAfCYldQl0q3dONp0GGexCGGgRAUsENxR4wWjnUg3SH1zMbvtrGb4k0hA==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGg8gkrwtrej1vEPOAul53aaA9IojixSPCuP35IYHGNWsLubZEWh7NnoVOGr-o-mc5V7-5yRztO_LQF62rMYfb7Dtph9UJBI7KApPhPj0eldPQjDAp52ObuUlhzesibQUVsNfQ_2w_-aNCm