[インデックス 1370] ファイルの概要
このコミットは、Go言語のコンパイラにおけるスライス(動的配列)とnil
の比較に関する挙動を修正・改善するものです。具体的には、[]
(空のスライスまたはスライス型)とnil
の比較が正しく行われるように、コンパイラのコード生成、定数処理、および型チェックの各フェーズにわたる変更が加えられています。
コミット
commit dcc064fe941b17dad67a13cd635c76a04cde7a69
Author: Ken Thompson <ken@golang.org>
Date: Thu Dec 18 21:33:45 2008 -0800
cmp [] to nil
R=r
OCL=21567
CL=21567
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dcc064fe941b17dad67a13cd635c76a04cde7a69
元コミット内容
このコミットの元のメッセージは「cmp [] to nil」です。これは「スライスとnilの比較」を意味し、Go言語においてスライスがnil
と比較された際のコンパイラの挙動を修正・実装する意図が示されています。
変更の背景
Go言語の初期開発段階において、スライスは言語の重要な要素として導入されました。スライスは、配列の一部を参照する軽量なデータ構造であり、その柔軟性から頻繁に利用されます。他の多くのデータ型と同様に、スライスもnil
というゼロ値を持つことができます。nil
スライスは、基盤となる配列へのポインタがnil
であり、長さと容量が0である状態を指します。
このコミットが作成された2008年12月は、Go言語がまだ一般公開される前の初期段階でした。当時のコンパイラ(6g
など)は、スライスとnil
の比較(例: mySlice == nil
)を適切に処理するためのロジックが完全に実装されていなかった可能性があります。このような比較は、スライスが初期化されていないか、意図的にnil
に設定されているかをチェックする上で非常に一般的かつ重要な操作です。
このコミットの背景には、以下の問題意識があったと考えられます。
- 比較の欠如または不正確な挙動: スライスと
nil
の比較がコンパイラによってサポートされていないか、あるいは誤ったコードが生成される可能性があった。 - 言語仕様の明確化: スライスと
nil
の比較がGo言語のセマンティクスとして明確に定義され、コンパイラがそれに従う必要があった。 - 開発者の利便性: 開発者がスライスの状態を簡単にチェックできるように、
slice == nil
という自然な構文をサポートする必要があった。
このコミットは、これらの課題に対処し、スライスとnil
の比較がGo言語の期待されるセマンティクスに沿って正しく機能するようにするための基盤を構築したものです。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語およびコンパイラに関する前提知識が必要です。
-
Go言語のスライス (Slice):
- スライスは、Go言語における可変長シーケンス型です。内部的には、基盤となる配列へのポインタ、長さ(
len
)、容量(cap
)の3つの要素から構成されます。 nil
スライス: スライスのゼロ値はnil
です。nil
スライスは、基盤となる配列へのポインタがnil
であり、長さと容量が0です。var s []int
のように宣言されたスライスは初期値としてnil
になります。- スライスの比較: Go言語では、スライス同士を直接比較することはできません(要素の比較は可能)。しかし、スライスが
nil
であるかどうかをチェックするために、slice == nil
またはslice != nil
という比較は可能です。この比較は、スライスの内部ポインタがnil
であるかどうかをチェックします。
- スライスは、Go言語における可変長シーケンス型です。内部的には、基盤となる配列へのポインタ、長さ(
-
Goコンパイラの構造: Goコンパイラ(当時の
6g
など)は、大きく分けて以下のフェーズで動作します。- フロントエンド (Frontend): ソースコードの字句解析、構文解析、抽象構文木 (AST) の構築、型チェック、定数畳み込み (constant folding) などを行います。
src/cmd/gc/const.c
やsrc/cmd/gc/walk.c
はこのフェーズに関連します。 - バックエンド (Backend): ASTを中間表現に変換し、ターゲットアーキテクチャ(この場合は
amd64
、当時の6g
)の機械語コードを生成します。src/cmd/6g/cgen.c
はこのフェーズに関連します。
- フロントエンド (Frontend): ソースコードの字句解析、構文解析、抽象構文木 (AST) の構築、型チェック、定数畳み込み (constant folding) などを行います。
-
抽象構文木 (AST):
- ソースコードはコンパイラによってASTに変換されます。ASTはプログラムの構造を木構造で表現したものです。コンパイラはASTを走査(walk)しながら、型チェックやコード生成を行います。
-
nil
リテラル (Wlitnil
):- コンパイラ内部で
nil
というキーワードがリテラルとして扱われる際の表現です。
- コンパイラ内部で
-
Array
構造体 (内部表現):- Go言語のスライスは、コンパイラ内部では通常、基盤となる配列へのポインタ、長さ、容量を保持する構造体として表現されます。このコミットのコードでは、
offsetof(Array, array)
という記述があり、これはスライスの内部表現であるArray
構造体の中に、実際のデータが格納されている配列へのポインタを指すarray
というフィールドが存在することを示唆しています。offsetof
はC言語のマクロで、構造体のメンバのオフセット(先頭からのバイト数)を計算します。
- Go言語のスライスは、コンパイラ内部では通常、基盤となる配列へのポインタ、長さ、容量を保持する構造体として表現されます。このコミットのコードでは、
技術的詳細
このコミットは、Goコンパイラの複数のコンポーネントにわたる変更を通じて、スライスとnil
の比較を正しく処理できるようにしています。
1. src/cmd/6g/cgen.c
の変更 (コード生成)
このファイルは、amd64
アーキテクチャ(当時の6g
コンパイラ)向けのコード生成を担当しています。変更の核心は、スライスとnil
の比較が行われた際に、スライスの基盤となる配列へのポインタがnil
(アドレス0
)であるかどうかをチェックする機械語コードを生成する点にあります。
追加されたコードブロックは以下の処理を行います。
if(isdarray(nl->type))
: 比較の左辺(nl
)の型が動的配列(スライス)であるかをチェックします。a = optoas(a, types[tptr])
: 比較演算(例:==
)をポインタ型に対するアセンブリ命令に変換します。regalloc(&n1, types[tptr], N); agen(nl, &n1);
: スライスのアドレスを保持するためのレジスタn1
を割り当て、スライスの値(スライスヘッダ)をn1
にロードするコードを生成します。n2 = n1; n2.op = OINDREG; n2.xoffset = offsetof(Array,array);
: ここが重要です。n1
にはスライスヘッダのアドレスが格納されています。OINDREG
は間接参照(ポインタのデリファレンス)を意味し、offsetof(Array,array)
はスライスヘッダ構造体内のarray
フィールド(基盤となる配列へのポインタ)のオフセットを示します。これにより、n2
はスライスの基盤となる配列へのポインタを指すようになります。nodconst(&tmp, types[tptr], 0);
: ポインタ型のnil
を表す定数0
を作成します。gins(optoas(OCMP, types[tptr]), &n2, &tmp);
:n2
(スライスの基盤配列ポインタ)とtmp
(nil
を表す0
)を比較するアセンブリ命令(CMP
)を生成します。patch(gbranch(a, types[tptr]), to);
: 比較結果に基づいて条件分岐命令を生成し、適切なターゲット(to
)へジャンプするようにパッチを適用します。regfree(&n1);
: 使用したレジスタを解放します。
この変更により、mySlice == nil
というGoのコードは、内部的にmySlice
の基盤配列ポインタが0
であるかどうかの比較に変換され、正しい機械語コードが生成されるようになります。
2. src/cmd/gc/const.c
の変更 (定数処理)
このファイルは、コンパイラの定数畳み込み(constant folding)と型変換を担当しています。convlit1
関数は、リテラルを特定の型に変換する際のルールを定義しています。
変更点:
case Wlitnil:
:nil
リテラルが処理される部分です。- 以前は、
nil
リテラルはポインタ型 (isptr[et]
) またはインターフェース型 (et == TINTER
) にのみ変換可能でした。 - 新しいコードでは、
switch(et)
文が導入され、TPTR32
(32ビットポインタ),TPTR64
(64ビットポインタ),TINTER
(インターフェース), そして**TARRAY
(配列型)** がnil
リテラルの有効な変換先として明示的に追加されました。
Go言語において、スライスは配列の上に構築されており、nil
スライスは基盤となる配列へのポインタがnil
である状態です。この変更は、コンパイラがnil
を配列型(ひいてはスライス型)の文脈で正しく解釈し、比較などの操作においてnil
が有効な値として扱われるようにするために不可欠です。これにより、nil
がスライスと比較される際に、型システムがその操作を許可するようになります。
3. src/cmd/gc/walk.c
の変更 (ASTウォーカー)
このファイルは、コンパイラのASTウォーカーの一部であり、ASTを走査しながら型チェックやその他のセマンティック分析を行います。
変更点:
case ONE:
: このケースは、単項演算子や比較演算子などの特定のノードタイプを処理する際に使用されます。if(isdarray(n->left->type)) { t = types[TBOOL]; break; }
: 比較の左辺(n->left
)の型が動的配列(スライス)である場合、その比較式全体の型をTBOOL
(ブーリアン型)に設定します。
この変更は、mySlice == nil
のような比較式の結果が常にブーリアン値(true
またはfalse
)になることを保証します。コンパイラがこの比較を正しく型付けすることで、後続のコード生成フェーズで適切な命令が生成されるようになります。
コアとなるコードの変更箇所
src/cmd/6g/cgen.c
(L750付近)
if(isdarray(nl->type)) {
a = optoas(a, types[tptr]);
regalloc(&n1, types[tptr], N);
agen(nl, &n1);
n2 = n1;
n2.op = OINDREG;
n2.xoffset = offsetof(Array,array);
nodconst(&tmp, types[tptr], 0);
gins(optoas(OCMP, types[tptr]), &n2, &tmp);
patch(gbranch(a, types[tptr]), to);
regfree(&n1);
break;
}
src/cmd/gc/const.c
(L63付近)
case Wlitnil:
if(isptrto(t, TSTRING))
goto bad1;
switch(et) {
default:
goto bad1;
case TPTR32:
case TPTR64:
case TINTER:
case TARRAY: // <-- 追加
break;
}
break;
src/cmd/gc/walk.c
(L1033付近)
case ONE:
if(n->left->type == T)
goto ret;
if(isdarray(n->left->type)) { // <-- 追加
t = types[TBOOL]; // <-- 追加
break; // <-- 追加
}
et = n->left->type->etype;
if(!okforeq[et])
goto badt;
コアとなるコードの解説
これらの変更は、Goコンパイラがスライスとnil
の比較をエンドツーエンドで処理できるようにするためのものです。
-
src/cmd/6g/cgen.c
: このコードは、スライスとnil
の比較が行われた際に、実際に実行される機械語命令を生成する部分です。スライスの内部構造(Array
構造体)から基盤となる配列へのポインタを取り出し、そのポインタがnil
(アドレス0
)であるかどうかを比較する命令を生成します。これにより、mySlice == nil
というGoのコードが、効率的かつ正確な低レベルの比較に変換されます。 -
src/cmd/gc/const.c
: この変更は、コンパイラの型システムがnil
リテラルを配列型(スライス型)の文脈で正しく認識できるようにします。以前は、nil
はポインタやインターフェースにしか関連付けられませんでしたが、この変更により、スライスがnil
と比較される際に型エラーが発生しなくなります。これは、コンパイラがnil
スライスの概念をより深く理解したことを示しています。 -
src/cmd/gc/walk.c
: このコードは、ASTを走査する際に、スライスとnil
の比較式の型をbool
に設定します。これにより、コンパイラはこのような比較がブーリアン結果を生成することを認識し、後続のコンパイルフェーズで適切な処理が行われることを保証します。これは、Go言語の型システムがスライスとnil
の比較を有効な操作として受け入れるための重要なステップです。
これらの変更が連携することで、Go言語においてmySlice == nil
という比較が、言語仕様に準拠し、かつ効率的にコンパイル・実行されるようになります。
関連リンク
- Go言語の公式ドキュメント - Slices: https://go.dev/blog/slices-intro (Go言語のスライスに関する基本的な情報)
- Go言語の公式ドキュメント - The Go Programming Language Specification (Slices): https://go.dev/ref/spec#Slice_types (Go言語のスライスに関する公式仕様)
- Go言語の公式ドキュメント - The Go Programming Language Specification (Comparison operators): https://go.dev/ref/spec#Comparison_operators (Go言語の比較演算子に関する公式仕様、特にスライスと
nil
の比較について言及されているはずです)
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Go言語のコンパイラに関する一般的な情報源 (例: Go compiler internals, Go compiler architecture などで検索)
- C言語の
offsetof
マクロに関する情報: https://en.cppreference.com/w/c/language/offsetof