[インデックス 1647] ファイルの概要
このコミットは、Goコンパイラのsrc/cmd/gc/walk.c
ファイルに対する変更です。具体的には、walk.c
内のascompatte
関数におけるfunarg
(関数引数、特にクロージャや関数リテラル)の戻り値の処理に関するバグ修正を目的としています。
コミット
commit a6c59ce2744182a1b6badd78c4269d1b424dd080
Author: Russ Cox <rsc@golang.org>
Date: Sun Feb 8 11:01:52 2009 -0800
gc funarg return fix.
change type (to satisfy OAS) after nodarg:
nodarg uses offset from type too,
and must use correct offset.
R=ken
OCL=24656
CL=24656
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a6c59ce2744182a1b6badd78c4269d1b424dd080
元コミット内容
gc funarg return fix.
change type (to satisfy OAS) after nodarg:
nodarg uses offset from type too,
and must use correct offset.
変更の背景
このコミットは、Goコンパイラの初期段階における重要なバグ修正です。funarg
(関数引数、特にクロージャや関数リテラル)の戻り値の処理において、コンパイラが誤った型情報を使用する可能性がありました。
具体的には、nodarg
というコンパイラ内部の関数が、ノードのオフセットを計算する際に型情報に依存していました。しかし、nodarg
が呼び出された後に、そのノードの型が変更される場合があり、その結果、OAS
(Order of Abstract Syntax Tree、ASTの順序付け)フェーズで型の一貫性が失われ、コンパイルエラーや不正なコード生成につながる可能性がありました。
この問題は、特にfunarg
のような複雑な型を持つ構造が関わる場合に顕在化しやすかったと考えられます。コンパイラが正しく型を認識し、それに基づいてオフセットを計算することは、メモリレイアウトの正確性やランタイムの動作に直結するため、この修正はコンパイラの安定性と正確性を確保するために不可欠でした。
前提知識の解説
このコミットを理解するためには、Goコンパイラの初期の構造と、いくつかの内部概念について知る必要があります。
-
Goコンパイラの初期構造 (
walk.c
): Goコンパイラは、初期にはC言語で書かれていました。src/cmd/gc/walk.c
は、そのC言語時代のコンパイラの一部であり、AST (Abstract Syntax Tree) の変換を行う「walk」フェーズを担当していました。現在のGoコンパイラでは、この機能は主にcmd/compile/internal/walk
パッケージ(Go言語で記述)に移行しています。 「walk」フェーズの主な役割は以下の通りです。- 複雑なステートメントの分解: 高レベルなGoのステートメント(例:
switch
文、map
操作、チャネル通信)を、より単純な個別の操作に分解します。これは「order」または「order of evaluation」とも呼ばれます。 - 高レベルな構造のデシュガー: Goの高レベルな機能(例:
defer
、range
)を、よりプリミティブな形式やGoランタイムへの呼び出しに変換します。
- 複雑なステートメントの分解: 高レベルなGoのステートメント(例:
-
nodarg
:nodarg
は、Goコンパイラの内部で使われる関数または概念で、特定のノード(ASTの要素)に関連する引数やオフセットを処理するために使用されます。コミットメッセージにあるように、「nodarg
uses offset from type too」とあることから、この関数が型情報に基づいてメモリ上のオフセットを計算する役割を担っていたことがわかります。これは、構造体のフィールドや関数の引数/戻り値のメモリ配置を決定する上で重要です。 -
OAS
(Order of Abstract Syntax Tree):OAS
は、ASTのノードが評価される順序を指します。Goコンパイラの「walk」フェーズの一部として、ASTの各ノードが正しい順序で処理され、適切な中間表現に変換されることを保証します。型の不一致やオフセットの誤りは、このOAS
フェーズでの処理に問題を引き起こす可能性があります。 -
funarg
(Functional Argument): Go言語におけるfunarg
は、関数を引数として渡すこと、または関数リテラル(クロージャ)を指します。Goはファーストクラス関数をサポートしており、関数を他のデータ型と同様に変数に代入したり、引数として渡したり、戻り値として返したりできます。クロージャは、それが定義されたスコープの変数をキャプチャできるため、コンパイラはこれらの変数のメモリ配置やライフタイムを正確に管理する必要があります。 -
eqtypenoname
: これは、Goコンパイラの内部で型を比較するために使用される関数です。eqtypenoname(type1, type2)
は、2つの型が名前を除いて等しいかどうかをチェックします。これは、型エイリアスや匿名構造体など、名前が異なるが構造的に同じ型を比較する際に重要になります。
技術的詳細
このコミットが修正しようとしている問題は、nodarg
がノードのオフセットを計算する際に使用する型情報と、その後のOAS
フェーズでの型の整合性に関するものです。
元のコードでは、以下の条件が満たされた場合に、convas
関数(おそらく型変換を行う関数)を呼び出していました。
if(l != T && r != N
&& structnext(&peekl) != T
&& listnext(&peekr) == N
&& eqtypenoname(r->type, *nl))
return convas(nod(OAS, nodarg(r->type, fp), r));
ここで問題となるのは、nodarg(r->type, fp)
が呼び出された後、nod(OAS, ..., r)
が構築される前に、nodarg
が使用したr->type
が、OAS
フェーズで期待される型と異なる場合があるという点です。特に、funarg
のような複雑な型の場合、型推論や型変換の過程で、nodarg
が参照する型と、最終的にそのノードが持つべき型との間に不一致が生じる可能性がありました。
nodarg
は、ノードのメモリ上のオフセットを計算するために型情報を使用します。もしnodarg
が呼び出された時点の型が、そのノードが最終的に持つべき型と異なると、計算されたオフセットが不正になり、結果としてメモリレイアウトの誤りや、ランタイムでのデータアクセスエラーを引き起こす可能性があります。
このコミットの修正は、nodarg
によって生成されたノードa
に対して、明示的にa->type = r->type;
という行を追加することで、この問題に対処しています。これにより、nodarg
がオフセットを計算した後に、そのノードa
の型を、右辺のノードr
の型に強制的に設定し直しています。これは、OAS
フェーズが期待する型と、nodarg
が使用した型との間の不一致を解消し、コンパイラ内部での型の一貫性を保つための重要なステップです。
この修正は、コンパイラがfunarg
のような複雑な構造を扱う際に、型とメモリレイアウトの正確性を保証するために不可欠でした。
コアとなるコードの変更箇所
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -1905,8 +1905,11 @@ ascompatte(int op, Type **nl, Node **nr, int fp)\n if(l != T && r != N\n && structnext(&peekl) != T\n && listnext(&peekr) == N\n-\t&& eqtypenoname(r->type, *nl))\n-\t\treturn convas(nod(OAS, nodarg(r->type, fp), r));\n+\t&& eqtypenoname(r->type, *nl)) {\n+\t\ta = nodarg(*nl, fp);\n+\t\ta->type = r->type;\n+\t\treturn convas(nod(OAS, a, r));\n+\t}\n \n loop:\n \tif(l != T && isddd(l->type)) {
コアとなるコードの解説
変更はsrc/cmd/gc/walk.c
ファイルのascompatte
関数内にあります。
元のコードでは、特定の条件(l
, r
が有効なノードであること、structnext
やlistnext
のチェック、そしてr
の型と*nl
の型が名前を除いて等しいこと)が満たされた場合に、以下の行が実行されていました。
return convas(nod(OAS, nodarg(r->type, fp), r));
この行は、nodarg(r->type, fp)
を呼び出して新しいノードを作成し、それをOAS
ノードの左辺として、右辺にr
を置いてconvas
関数に渡していました。
修正後のコードでは、この部分が以下のように変更されています。
a = nodarg(*nl, fp);
a->type = r->type;
return convas(nod(OAS, a, r));
この変更のポイントは以下の2点です。
-
nodarg
の引数の変更: 元のコードではnodarg(r->type, fp)
と、右辺のノードr
の型を直接渡していました。修正後ではnodarg(*nl, fp)
と、ascompatte
関数の引数として渡された*nl
(おそらく期待される型)を渡しています。これは、nodarg
がノードを生成する際に、より正確な(または期待される)型情報に基づいてオフセットを計算するように意図されたものと考えられます。 -
a->type = r->type;
の追加: これがこのコミットの最も重要な変更点です。nodarg
によって生成されたノードa
の型を、明示的に右辺のノードr
の型に設定し直しています。 これは、nodarg
がノードを生成する際に一時的に使用する型と、そのノードが最終的に持つべき型(この場合は右辺r
の型)との間に不一致が生じる可能性があったためです。特に、funarg
のような複雑な型の場合、型推論や型変換の過程で、nodarg
が参照する型と、最終的にそのノードが持つべき型との間に不一致が生じる可能性がありました。 この行を追加することで、nodarg
がオフセットを計算した後に、そのノードa
の型を、右辺のノードr
の型に強制的に設定し直しています。これにより、OAS
フェーズが期待する型と、nodarg
が使用した型との間の不一致を解消し、コンパイラ内部での型の一貫性を保つための重要なステップとなります。
この修正により、funarg
の戻り値の型が正しく処理され、nodarg
が計算するオフセットが常に正しい型情報に基づいていることが保証され、コンパイラの安定性と正確性が向上しました。