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

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

このコミットは、Goコンパイラの初期バージョン(gc、Go Compilerの略)の一部であったsrc/cmd/gc/walk.cファイルに対する変更です。walk.cは、Goコンパイラの「walk」フェーズを担う重要なファイルでした。このフェーズは、コンパイルプロセスの最終段階の一つであり、Goのソースコードから生成された中間表現(IR)を、より低レベルな操作に分解し、高レベルな言語構造(switch文、map操作、チャネル操作など)を、より基本的な操作やGoランタイムへの呼び出しに変換(desugar)する役割を担っていました。具体的には、複雑なステートメントを単純な個別の操作に分解し、一時変数を管理し、評価の順序を保証していました。

コミット

このコミットは、インターフェース型と構造体型の変換処理の順序を変更し、これらの変換を「最後の手段」として試みるように修正しています。これは、変換が常に実際の使用を伴うという仮定が、不必要な処理を引き起こす可能性があったためです。

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

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

元コミット内容

don't try interface/structure conversion until
last resort, because it assumes every call is a
real use of the conversion.

R=ken
OCL=19026
CL=19026

変更の背景

この変更の背景には、Goコンパイラが型変換を処理する際の効率性と正確性の問題がありました。元の実装では、インターフェース型と構造体型の変換が、他のより具体的な型変換の試行よりも早い段階で行われていました。コミットメッセージにある「it assumes every call is a real use of the conversion」という記述は、コンパイラがこれらの変換を試みる際に、それが常に必要かつ正当な操作であると仮定していたことを示唆しています。

しかし、この仮定は常に正しいとは限りませんでした。インターフェースや構造体の変換は、他のより単純な型変換(例えば、配列型間の変換や構造体リテラルの処理)よりも複雑で、計算コストが高い場合があります。また、不必要な変換の試行は、コンパイル時間の増加や、場合によっては誤った型解決につながる可能性がありました。

このコミットは、インターフェースと構造体の変換を「最後の手段 (last resort)」として扱うことで、より具体的で効率的な変換パスが先に試行されるように順序を調整しています。これにより、コンパイラはまず最も適切な変換を試み、それが失敗した場合にのみ、より汎用的なインターフェース/構造体変換にフォールバックするようになります。これは、コンパイラの型解決ロジックを最適化し、不必要な処理を削減することを目的としています。

前提知識の解説

  • Goコンパイラ (gc): Go言語の公式コンパイラの一つで、Goのソースコードを機械語に変換する役割を担います。初期のgcはC言語で書かれていましたが、後にGo言語自体で書き直されました。
  • src/cmd/gc/walk.c: gcコンパイラの「walk」フェーズを実装していたC言語のソースファイルです。このフェーズは、抽象構文木(AST)を走査し、高レベルなGoの構文をコンパイラが処理しやすい低レベルな中間表現に変換する役割を担っていました。型チェック、型変換、組み込み関数の展開などがこのフェーズで行われます。
  • Goの型システム: Goは静的型付け言語であり、厳格な型システムを持っています。
    • インターフェース型: 振る舞いを定義する型で、メソッドのシグネチャの集合です。任意の具象型がインターフェースのすべてのメソッドを実装していれば、そのインターフェース型として扱えます。
    • 構造体型: 異なる型のフィールドをまとめた複合型です。
    • 型変換: ある型の値を別の型の値に変換する操作です。Goでは明示的な型変換(キャスト)が必要な場合と、コンパイラが自動的に行う暗黙的な変換があります。このコミットで扱われているのは、コンパイラ内部での型解決と変換のロジックです。
  • indir: コンパイラ内部の関数で、おそらくポインタの逆参照(indirection)や、値への間接的なアクセスを処理する役割を持つと推測されます。
  • ifaceop: インターフェース関連の操作(interface operation)を行うコンパイラ内部の関数と推測されます。インターフェース型への変換や、インターフェース値の操作に関わる可能性があります。
  • isandss: is and ssの略であると推測され、型がインターフェース型(interface)または構造体型(structure)であるか、あるいはそれらの変換が可能であるかをチェックするコンパイラ内部の関数であると考えられます。et != Inoneという条件から、変換の種類を示す列挙型のようなものを返すと考えられます。
  • goto ret: C言語の構文で、retというラベルに処理をジャンプさせます。このコンテキストでは、型変換が成功した場合に、現在の処理を終了して呼び出し元に戻るために使用されています。

技術的詳細

このコミットの技術的な核心は、src/cmd/gc/walk.c内のloop:ブロックにおける型変換ロジックの順序変更です。具体的には、インターフェース型と構造体型の変換を試みるコードブロックが、ファイルのより下部、つまり他の特定の型変換(配列型間の変換や構造体リテラルの処理)の後に移動されました。

元のコードでは、loop:の冒頭近くで、配列型間のポインタ変換のチェックの直後にインターフェース/構造体変換が試みられていました。

// (元の位置)
// interface and structure
et = isandss(n->type, l);
if(et != Inone) {
    indir(n, ifaceop(n->type, l, et));
    goto ret;
}

このコミットにより、上記のコードブロックは、// structure literal(構造体リテラル)と、// array(配列)の変換ロジックの後に移動されました。

// (新しい位置)
// structure literal
if(t->etype == TSTRUCT) {
    indir(n, structlit(n));
    goto ret;
}

// array
if(isptrarray(t) && isptrdarray(l->type)) {
    indir(n, arrayconv(n));
    goto ret;
}

// interface and structure
et = isandss(n->type, l);
if(et != Inone) {
    indir(n, ifaceop(n->type, l, et));
    goto ret;
}

この変更の技術的な意味合いは以下の通りです。

  1. 変換順序の優先順位付け: インターフェース/構造体変換は、より汎用的な変換パスです。これを他の特定の変換(構造体リテラルや配列変換)の後に配置することで、コンパイラはまずより具体的で、おそらくより効率的な変換を試みるようになります。
  2. 不必要な処理の回避: コミットメッセージが示唆するように、インターフェース/構造体変換は「every call is a real use of the conversion」と仮定していました。これは、実際には変換が不要な場合でも試行されていた可能性を意味します。順序を変更することで、より適切な変換パスが先に成功し、インターフェース/構造体変換の試行が完全にスキップされるケースが増える可能性があります。
  3. コンパイラの効率化: 不必要な変換の試行を減らすことで、コンパイル時間の短縮や、コンパイラ内部のリソース消費の最適化に貢献します。
  4. 正確性の向上: 特定の型変換がより適切な順序で処理されることで、型解決の正確性が向上し、予期せぬコンパイルエラーやランタイムエラーのリスクが低減される可能性があります。

この変更は、Goコンパイラの型システムと型変換ロジックの成熟を示すものであり、初期のコンパイラ開発における継続的な最適化とバグ修正の一環として行われました。

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

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -597,13 +597,6 @@ loop:
 		if(isptrarray(t) && isptrdarray(l->type))
 			goto ret;
 
-		// interface and structure
-		et = isandss(n->type, l);
-		if(et != Inone) {
-			indir(n, ifaceop(n->type, l, et));
-			goto ret;
-		}
-
 		// structure literal
 		if(t->etype == TSTRUCT) {
 			indir(n, structlit(n));
@@ -624,6 +617,13 @@ loop:
 			goto ret;
 		}
 
+		// interface and structure
+		et = isandss(n->type, l);
+		if(et != Inone) {
+			indir(n, ifaceop(n->type, l, et));
+			goto ret;
+		}
+
 		if(l->type != T)
 			yyerror("cannot convert %T to %T", l->type, t);
 		goto ret;

コアとなるコードの解説

上記のdiffは、src/cmd/gc/walk.cファイル内のloop:というラベルが付いたコードブロックにおける変更を示しています。

  • 削除された部分 (-で始まる行):

    -		// interface and structure
    -		et = isandss(n->type, l);
    -		if(et != Inone) {
    -			indir(n, ifaceop(n->type, l, et));
    -			goto ret;
    -		}
    

    このブロックは、インターフェース型と構造体型の変換を試みるロジックです。isandss関数で変換の可能性をチェックし、etInone(変換なし)でなければ、ifaceop関数を使って実際の変換を行い、indirで結果を処理し、goto retで処理を終了していました。このブロックが元の位置から削除されました。

  • 追加された部分 (+で始まる行):

    +		// interface and structure
    +		et = isandss(n->type, l);
    +		if(et != Inone) {
    +			indir(n, ifaceop(n->type, l, et));
    +			goto ret;
    +		}
    

    全く同じコードブロックが、// structure literal(構造体リテラルの処理)と、// array(配列の変換処理)の後に再配置されています。

この変更の核心は、コードの移動であり、その結果として型変換の順序が変わったことです。

  • 移動前: インターフェース/構造体変換は、配列ポインタ変換の直後に試行されていました。
  • 移動後: インターフェース/構造体変換は、構造体リテラルと配列変換の後に試行されるようになりました。

これにより、コンパイラはまず、より具体的で直接的な型変換(構造体リテラルや配列変換)を試み、それらが適用できない場合にのみ、より汎用的なインターフェース/構造体変換のロジックに進むようになります。これは、コンパイラの型解決アルゴリズムにおける優先順位を調整し、不必要な変換の試行を減らすための最適化です。

関連リンク

参考にした情報源リンク

  • Go compiler src/cmd/gc/walk.c の役割に関するWeb検索結果
  • Go言語の型システムに関する一般的な知識
  • C言語の構文(gotoなど)に関する一般的な知識