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

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

このコミットは、Goコンパイラのsrc/cmd/gc/walk.cファイルにおけるインターフェース変換(型アサーション)の処理に関する変更です。具体的には、多値返却を伴う型アサーション(例: T, ok = I.(T))をサポートするための内部的な変更が含まれています。

コミット

commit a8b56a73a4cef61bf5a87db07bfda1f1705dd873
Author: Ken Thompson <ken@golang.org>
Date:   Wed Nov 5 14:27:07 2008 -0800

    T,ok = I.(T)
    
    R=r
    OCL=18580
    CL=18582

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

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

元コミット内容

コミットメッセージは非常に簡潔で、「T,ok = I.(T)」というGo言語の構文を示しています。これは、インターフェースの型アサーションにおいて、値と成功を示すブール値の2つの値を返す形式を指しています。このコミットは、Goコンパイラがこの形式の型アサーションを正しく処理できるようにするための変更であることを示唆しています。

変更の背景

Go言語の初期開発段階において、インターフェースの型アサーションは重要な機能でした。型アサーションは、インターフェース型の変数が特定の具象型を保持しているかどうかを確認し、もしそうであればその具象型に変換するために使用されます。

Go言語には、型アサーションの構文が2種類あります。

  1. value := interfaceValue.(Type): これは、interfaceValueType型でない場合にパニック(panic)を引き起こします。
  2. value, ok := interfaceValue.(Type): これは、interfaceValueType型でない場合でもパニックを引き起こさず、okというブール値で成功/失敗を示します。この形式は「comma-ok idiom」として知られ、エラーハンドリングや型チェックの際に非常に便利です。

このコミットが行われた2008年11月は、Go言語がまだ一般に公開される前の初期段階でした。この時期には、言語の基本的な構文やセマンティクスが固められており、コンパイラもそれらの機能をサポートするように進化していました。このコミットは、特に後者の「comma-ok idiom」形式の型アサーションをコンパイラが正しく処理できるようにするための内部的な修正であると考えられます。

前提知識の解説

Go言語のインターフェース

Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。具象型がインターフェースのすべてのメソッドを実装していれば、その具象型は自動的にそのインターフェースを満たします。インターフェースは、ポリモーフィズムを実現し、柔軟なコード設計を可能にします。

型アサーション (Type Assertion)

型アサーションは、インターフェース型の変数が基となる具象型にアクセスするために使用されます。

  • x.(T): インターフェース値xが型Tを保持していることをアサートします。もしxTを保持していなければ、ランタイムパニックが発生します。
  • x.(T)の多値返却形式: v, ok := x.(T)。この形式では、xが型Tを保持している場合、vにはT型の値が、okにはtrueが設定されます。xが型Tを保持していない場合、vにはT型のゼロ値が、okにはfalseが設定され、パニックは発生しません。

Goコンパイラ (gc) の内部構造 (2008年頃)

Goコンパイラ(gc)は、Go言語のソースコードを機械語に変換するツールチェーンの一部です。gcは、複数のステージを経てコンパイルを行います。

  • パーシング: ソースコードを抽象構文木(AST)に変換します。
  • 型チェック: ASTの各ノードの型を解決し、型エラーを検出します。
  • 中間表現 (IR) への変換: ASTをコンパイラ内部で扱いやすい中間表現に変換します。
  • 最適化: IRに対して様々な最適化を適用します。
  • コード生成: 最適化されたIRからターゲットアーキテクチャの機械語を生成します。

src/cmd/gc/walk.cは、コンパイラの「ウォーク」フェーズ、つまりASTを走査し、中間表現への変換や一部の最適化を行う部分に関連しています。このファイルは、Go言語の様々な構文要素(例えば、型変換、関数呼び出し、制御フローなど)がどのように内部的に表現され、処理されるかを定義する重要な役割を担っています。

OCONV オペレーション

OCONVは、Goコンパイラの内部で型変換(conversion)を表すオペレーションコードです。インターフェースの型アサーションも、広義の型変換の一種としてOCONVオペレーションの下で処理されます。

ifaceop 関数

ifaceop関数は、インターフェース関連の操作(インターフェースから具象型への変換、具象型からインターフェースへの変換など)を処理するためのヘルパー関数です。この関数は、特定のインターフェース操作に対応するランタイムヘルパー関数(例: ifaceI2TifaceI2Iなど)を呼び出すためのコードを生成します。

技術的詳細

このコミットの主要な変更点は、src/cmd/gc/walk.cファイルにおけるインターフェースの型アサーション(OCONVオペレーション)の処理を拡張し、多値返却形式(T, ok = I.(T))をサポートすることです。

具体的には、以下の変更が行われています。

  1. 新しい内部オペレーションコードの導入:

    • enum定義にI2T2I2I2が追加されました。
      • I2T: インターフェースから具象型への変換(単一値返却)
      • I2T2: インターフェースから具象型への変換(多値返却、ok付き)
      • I2I: インターフェースから別のインターフェースへの変換(単一値返却)
      • I2I2: インターフェースから別のインターフェースへの変換(多値返却、ok付き) これらは、型アサーションが成功したかどうかを示すブール値(ok)を返す必要がある場合に、コンパイラが異なる内部パスを取ることを可能にします。
  2. OCONV処理の拡張:

    • walk.c内のwalk関数のOCONVケースに新しいロジックが追加されました。
    • cl == 2 && cr == 1という条件は、左辺が2つの値(T, ok)を受け取り、右辺が1つの値(I.(T)の結果)を生成する状況、つまり多値返却の型アサーションを検出しています。
    • isandss関数(is interface and same typeの略か?)が呼び出され、元の型アサーションがI2TまたはI2Iのどちらに分類されるかを判断します。
    • その結果に基づいて、新しいオペレーションコードI2T2またはI2I2に変換されます。これにより、コンパイラは多値返却を伴う型アサーションであることを認識し、適切なコード生成パスに進むことができます。
    • ifaceop関数が新しいオペレーションコード(I2T2またはI2I2)で呼び出され、ランタイムヘルパー関数への呼び出しを生成します。
  3. ifacename配列の追加:

    • ifacenameという文字列配列が追加され、I2T2I2I2に対応するランタイムヘルパー関数の名前(例: "ifaceI2T2""ifaceI2I2")が定義されました。これは、syslook関数でこれらのヘルパー関数をルックアップするために使用されます。
  4. ifaceop関数の修正:

    • ifaceop関数は、新しいオペレーションコードI2T2I2I2を処理するように変更されました。
    • 以前はI2TI2Iのケースが別々に処理されていましたが、この変更により、I2T, I2T2, I2I, I2I2のすべてが共通のロジックで処理されるようになりました。これは、これらの操作が本質的に似ており、主に返される値の数(単一か複数か)が異なるためです。
    • syslook(ifacename[op], 1)という行が追加され、opI2TI2T2など)に対応する名前を使って適切なランタイムヘルパー関数を動的にルックアップするようになりました。これにより、コードの重複が減り、拡張性が向上しています。

これらの変更により、GoコンパイラはT, ok = I.(T)のような多値返却の型アサーションを正しく解析し、対応するランタイムヘルパー関数(例: runtime.ifaceI2T2runtime.ifaceI2I2)を呼び出すためのコードを生成できるようになります。これらのランタイムヘルパー関数は、実際に型チェックを行い、成功した場合は変換された値を、失敗した場合はゼロ値とfalseを返します。

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

diff --git a/src/cmd/gc/walk.c b/src/cmd/gc/walk.c
index ceae4480a5..f382390627 100644
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -14,8 +14,10 @@ enum
 {
 	Inone,
 	I2T,
+	I2T2,
 	I2I,
-	T2I
+	I2I2,
+	T2I,
 };
 
 // can this code branch reach the end
@@ -463,6 +465,33 @@ loop:
 			goto ret;
 		}
 		break;
+
+		case OCONV:
+			if(cl == 2 && cr == 1) {
+				// a,b = i.(T)
+				if(r->left == N)
+					break;
+				et = isandss(r->type, r->left);
+				switch(et) {
+				case I2T:
+					et = I2T2;
+					break;
+				case I2I:
+					et = I2I2;
+					break;
+				default:
+					et = Inone;
+					break;
+				}
+				if(et == Inone)
+					break;
+				r = ifaceop(r->type, r->left, et);
+				l = ascompatet(n->op, &n->left, &r->type, 0);
+				if(l != N)
+					indir(n, list(r, reorder2(l)));
+				goto ret;
+			}
+			break;
 		}
 
 		switch(l->op) {
@@ -2667,6 +2696,15 @@ isandss(Type *lt, Node *r)
 	return Inone;
 }
 
+static	char*
+ifacename[] =\n{\n+\t[I2T]\t= \"ifaceI2T\",\n+\t[I2T2]\t= \"ifaceI2T2\",\n+\t[I2I]\t= \"ifaceI2I\",\n+\t[I2I2]\t= \"ifaceI2I2\",\n+};\n+\n Node*
 ifaceop(Type *tl, Node *n, int op)
 {
 	Node *a, *r, *on, *s;
@@ -2678,26 +2716,7 @@ ifaceop(Type *tl, Node *n, int op)
 
 	switch(op) {
 	default:
-\t\tfatal(\"ifaceop: unknown op %d\\n\", op);\n-\n-\tcase I2T:\n-\t\t// ifaceI2T(sigt *byte, iface any) (ret any);\n-\n-\t\ta = n;\t\t\t\t// interface\n-\t\tr = a;\n-\n-\t\ts = signame(tl);\t\t// sigi\n-\t\tif(s == S)\n-\t\t\tfatal(\"ifaceop: signame I2T\");\n-\t\ta = s->oname;\n-\t\ta = nod(OADDR, a, N);\n-\t\tr = list(a, r);\n-\n-\t\ton = syslook(\"ifaceI2T\", 1);\n-\t\targtype(on, tr);\n-\t\targtype(on, tl);\n-\n-\t\tbreak;\n+\t\tfatal(\"ifaceop: unknown op %O\\n\", op);\
 
 	case T2I:
 		// ifaceT2I(sigi *byte, sigt *byte, elem any) (ret any);
@@ -2726,22 +2745,26 @@ ifaceop(Type *tl, Node *n, int op)
 
 		break;
 
+\tcase I2T:\n+\tcase I2T2:\
 	case I2I:\
-\t\t// ifaceI2I(sigi *byte, iface any-1) (ret any-2);\n+\tcase I2I2:\
+\t\t// iface[IT]2[IT][2](sigt *byte, iface any) (ret any[, ok bool]);\n 
 		a = n;				// interface
 		r = a;
 
 		s = signame(tl);		// sigi
 		if(s == S)
-\t\t\tfatal(\"ifaceop: signame I2I\");\n+\t\t\tfatal(\"ifaceop: signame %d\", op);\
 		a = s->oname;
 		a = nod(OADDR, a, N);
 		r = list(a, r);
 
-\t\ton = syslook(\"ifaceI2I\", 1);\n+\t\ton = syslook(ifacename[op], 1);\
 		argtype(on, tr);
 		argtype(on, tl);
+\n 
 		break;
 
 	case OEQ:

コアとなるコードの解説

enum の変更

enum
{
	Inone,
	I2T,
+	I2T2, // 新しく追加されたインターフェースから具象型への変換(多値返却)
 	I2I,
-	T2I
+	I2I2, // 新しく追加されたインターフェースから別のインターフェースへの変換(多値返却)
+	T2I,
};

I2T2I2I2は、それぞれI.(T)形式の型アサーションがT, ok := I.(T)のように2つの値を返す場合に、コンパイラ内部で区別するための新しいオペレーションコードです。

walk 関数内のOCONV処理の変更

 		case OCONV:
 			if(cl == 2 && cr == 1) { // 左辺が2つの値、右辺が1つの値の場合(T, ok = I.(T))
 				// a,b = i.(T)
 				if(r->left == N)
 					break;
 				et = isandss(r->type, r->left); // 型アサーションの種類を判定
 				switch(et) {
 				case I2T:
 					et = I2T2; // I2TならI2T2に変換
 					break;
 				case I2I:
 					et = I2I2; // I2IならI2I2に変換
 					break;
 				default:
 					et = Inone;
 					break;
 				}
 				if(et == Inone)
 					break;
 				r = ifaceop(r->type, r->left, et); // 新しいオペレーションコードでifaceopを呼び出し
 				l = ascompatet(n->op, &n->left, &r->type, 0);
 				if(l != N)
 					indir(n, list(r, reorder2(l)));
 				goto ret;
 			}
 			break;

このブロックは、T, ok = I.(T)のような多値返却の型アサーションを検出した場合に実行されます。isandss関数で元の型アサーションがI2T(インターフェースから具象型)かI2I(インターフェースからインターフェース)かを判断し、それぞれI2T2またはI2I2に内部的なオペレーションコードを変換します。その後、ifaceop関数を呼び出して、対応するランタイムヘルパー関数への呼び出しを生成します。

ifacename 配列の追加

+static	char*
+ifacename[] =\n{\n+\t[I2T]\t= \"ifaceI2T\",\n+\t[I2T2]\t= \"ifaceI2T2\",\n+\t[I2I]\t= \"ifaceI2I\",\n+\t[I2I2]\t= \"ifaceI2I2\",\n+};\n+

この配列は、各内部オペレーションコード(I2T, I2T2, I2I, I2I2)に対応するランタイムヘルパー関数の名前をマッピングしています。これにより、ifaceop関数内でこれらの名前を動的に参照できるようになります。

ifaceop 関数の変更

 	switch(op) {
 	default:
-\t\tfatal(\"ifaceop: unknown op %d\\n\", op);\n-\n-\tcase I2T:\n-\t\t// ifaceI2T(sigt *byte, iface any) (ret any);\n-\n-\t\ta = n;\t\t\t\t// interface\n-\t\tr = a;\n-\n-\t\ts = signame(tl);\t\t// sigi\n-\t\tif(s == S)\n-\t\t\tfatal(\"ifaceop: signame I2T\");\n-\t\ta = s->oname;\n-\t\ta = nod(OADDR, a, N);\n-\t\tr = list(a, r);\n-\n-\t\ton = syslook(\"ifaceI2T\", 1);\n-\t\targtype(on, tr);\n-\t\targtype(on, tl);\n-\n-\t\tbreak;\n+\t\tfatal(\"ifaceop: unknown op %O\\n\", op);\
 
 	case T2I:
 		// ... (T2Iの処理は変更なし)
 
 		break;
 
+\tcase I2T:\n+\tcase I2T2:\
 	case I2I:\
-\t\t// ifaceI2I(sigi *byte, iface any-1) (ret any-2);\n+\tcase I2I2:\
+\t\t// iface[IT]2[IT][2](sigt *byte, iface any) (ret any[, ok bool]);\n 
 		a = n;				// interface
 		r = a;
 
 		s = signame(tl);		// sigi
 		if(s == S)
-\t\t\tfatal(\"ifaceop: signame I2I\");\n+\t\t\tfatal(\"ifaceop: signame %d\", op);\
 		a = s->oname;
 		a = nod(OADDR, a, N);
 		r = list(a, r);
 
-\t\ton = syslook(\"ifaceI2I\", 1);\n+\t\ton = syslook(ifacename[op], 1);\ // ifacename配列から動的にヘルパー関数名をルックアップ
 		argtype(on, tr);
 		argtype(on, tl);
+\n 
 		break;
 
 	case OEQ:

ifaceop関数は、I2T, I2T2, I2I, I2I2の各ケースを統合し、ifacename配列を使用して対応するランタイムヘルパー関数を動的にルックアップするように変更されました。これにより、コードの重複が削減され、保守性が向上しています。コメント// iface[IT]2[IT][2](sigt *byte, iface any) (ret any[, ok bool]);は、これらのヘルパー関数がインターフェースから具象型または別のインターフェースへの変換を行い、オプションでokブール値を返すことを示唆しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(特にsrc/cmd/gcディレクトリ)
  • Goコンパイラの歴史に関する一般的な知識
  • Go言語の型アサーションに関するブログ記事やチュートリアル
  • Go言語のランタイムヘルパー関数に関する情報(runtimeパッケージのソースコードなど)