[インデックス 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種類あります。
value := interfaceValue.(Type)
: これは、interfaceValue
がType
型でない場合にパニック(panic)を引き起こします。value, ok := interfaceValue.(Type)
: これは、interfaceValue
がType
型でない場合でもパニックを引き起こさず、ok
というブール値で成功/失敗を示します。この形式は「comma-ok idiom」として知られ、エラーハンドリングや型チェックの際に非常に便利です。
このコミットが行われた2008年11月は、Go言語がまだ一般に公開される前の初期段階でした。この時期には、言語の基本的な構文やセマンティクスが固められており、コンパイラもそれらの機能をサポートするように進化していました。このコミットは、特に後者の「comma-ok idiom」形式の型アサーションをコンパイラが正しく処理できるようにするための内部的な修正であると考えられます。
前提知識の解説
Go言語のインターフェース
Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。具象型がインターフェースのすべてのメソッドを実装していれば、その具象型は自動的にそのインターフェースを満たします。インターフェースは、ポリモーフィズムを実現し、柔軟なコード設計を可能にします。
型アサーション (Type Assertion)
型アサーションは、インターフェース型の変数が基となる具象型にアクセスするために使用されます。
x.(T)
: インターフェース値x
が型T
を保持していることをアサートします。もしx
がT
を保持していなければ、ランタイムパニックが発生します。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
関数は、インターフェース関連の操作(インターフェースから具象型への変換、具象型からインターフェースへの変換など)を処理するためのヘルパー関数です。この関数は、特定のインターフェース操作に対応するランタイムヘルパー関数(例: ifaceI2T
、ifaceI2I
など)を呼び出すためのコードを生成します。
技術的詳細
このコミットの主要な変更点は、src/cmd/gc/walk.c
ファイルにおけるインターフェースの型アサーション(OCONV
オペレーション)の処理を拡張し、多値返却形式(T, ok = I.(T)
)をサポートすることです。
具体的には、以下の変更が行われています。
-
新しい内部オペレーションコードの導入:
enum
定義にI2T2
とI2I2
が追加されました。I2T
: インターフェースから具象型への変換(単一値返却)I2T2
: インターフェースから具象型への変換(多値返却、ok
付き)I2I
: インターフェースから別のインターフェースへの変換(単一値返却)I2I2
: インターフェースから別のインターフェースへの変換(多値返却、ok
付き) これらは、型アサーションが成功したかどうかを示すブール値(ok
)を返す必要がある場合に、コンパイラが異なる内部パスを取ることを可能にします。
-
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
)で呼び出され、ランタイムヘルパー関数への呼び出しを生成します。
-
ifacename
配列の追加:ifacename
という文字列配列が追加され、I2T2
とI2I2
に対応するランタイムヘルパー関数の名前(例:"ifaceI2T2"
、"ifaceI2I2"
)が定義されました。これは、syslook
関数でこれらのヘルパー関数をルックアップするために使用されます。
-
ifaceop
関数の修正:ifaceop
関数は、新しいオペレーションコードI2T2
とI2I2
を処理するように変更されました。- 以前は
I2T
とI2I
のケースが別々に処理されていましたが、この変更により、I2T
,I2T2
,I2I
,I2I2
のすべてが共通のロジックで処理されるようになりました。これは、これらの操作が本質的に似ており、主に返される値の数(単一か複数か)が異なるためです。 syslook(ifacename[op], 1)
という行が追加され、op
(I2T
、I2T2
など)に対応する名前を使って適切なランタイムヘルパー関数を動的にルックアップするようになりました。これにより、コードの重複が減り、拡張性が向上しています。
これらの変更により、GoコンパイラはT, ok = I.(T)
のような多値返却の型アサーションを正しく解析し、対応するランタイムヘルパー関数(例: runtime.ifaceI2T2
やruntime.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,
};
I2T2
とI2I2
は、それぞれ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言語のインターフェース: https://go.dev/tour/methods/10
- Go言語の型アサーション: https://go.dev/tour/methods/15
- Go言語の
comma-ok idiom
: https://go.dev/blog/two-go-programs (古い記事ですが、概念は同じです)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(特に
src/cmd/gc
ディレクトリ) - Goコンパイラの歴史に関する一般的な知識
- Go言語の型アサーションに関するブログ記事やチュートリアル
- Go言語のランタイムヘルパー関数に関する情報(
runtime
パッケージのソースコードなど)