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

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

このコミットは、Goコンパイラ(gc)の内部における型宣言処理の変更に関するものです。具体的には、typedcl2という関数がsrc/cmd/gc/dcl.cから削除され、その機能がsrc/cmd/gc/export.c内のimporttype関数に統合されました。また、src/cmd/gc/go.hからtypedcl2の関数宣言が削除されています。

コミット

commit 882368939c3dc0e5a938bf9ca9f203391d88ffe9
Author: Luuk van Dijk <lvd@golang.org>
Date:   Tue Nov 29 13:34:08 2011 +0100

    gc: move typedcl2 into export.c
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5447043

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

https://github.com/golang/go/commit/882368939c3dc0e5a938bf9ca9f203391d88ffe9

元コミット内容

gc: move typedcl2 into export.c

変更の背景

このコミットは、Goコンパイラ(gc)の初期開発段階におけるコード整理と機能の適切な配置を目的としています。typedcl2関数は、主にインポート処理中に型を宣言する役割を担っていました。特に、unsafe.Pointerのような特殊な型の扱いが含まれていました。

元のdcl.cファイルは「宣言(declaration)」に関連するコードを扱う場所でしたが、typedcl2の機能がインポートされた型に特化しているため、型のエクスポート/インポートを扱うexport.cに移動することがより論理的であると判断されたと考えられます。これにより、コンパイラのコードベースのモジュール性が向上し、関連する機能が同じファイルに集約されることで、将来的なメンテナンスや理解が容易になります。

特に、unsafe.Pointerの特殊な処理がtypedcl2内に存在していたことから、インポート時に型の一貫性を保証しつつ、特定の組み込み型に対する特別な扱いを適切に行うための構造改善が意図されています。

前提知識の解説

このコミットを理解するためには、Goコンパイラ(gc)の基本的な構造と、型システム、特にインポートメカニズムに関する知識が必要です。

  1. Goコンパイラ (gc):

    • gcはGo言語の公式コンパイラであり、Goソースコードを機械語に変換します。
    • コンパイラは通常、字句解析、構文解析、意味解析、中間コード生成、最適化、コード生成といった複数のフェーズで構成されます。
    • このコミットが関連するのは、主に意味解析(型チェック、宣言処理)と、パッケージのインポート/エクスポートに関連する部分です。
  2. 型システムと型宣言:

    • Goは静的型付け言語であり、すべての変数や式には型があります。
    • 型宣言は、新しい型を定義したり、既存の型に別名を付けたりするプロセスです。
    • コンパイラは、プログラム内で使用されるすべての型が正しく定義され、一貫性があることを保証する必要があります。
  3. パッケージのインポート/エクスポート:

    • Goのプログラムはパッケージに分割され、他のパッケージの公開された型、関数、変数などをインポートして利用できます。
    • コンパイラは、インポートされたパッケージの型情報を読み込み、現在のコンパイルユニットの型システムに統合する必要があります。このプロセスでは、インポートされた型と現在のコンパイルユニットで定義されている型との間に矛盾がないかを確認します。
  4. unsafe.Pointer:

    • unsafe.PointerはGoのunsafeパッケージで提供される特殊な型です。これは、任意の型のポインタを保持できる汎用ポインタであり、Goの型システムをバイパスしてメモリを直接操作することを可能にします。
    • unsafe.Pointerは非常に強力ですが、誤用するとメモリ安全性を損なう可能性があるため、Goの通常の型システムとは異なる特別な扱いがコンパイラ内部で必要とされます。このコミットのコードにも、unsafe.Pointerに対する特別な処理が明示的に記述されています。
  5. コンパイラ内部のデータ構造:

    • Node: 抽象構文木(AST)のノードを表すデータ構造。プログラムの構文要素(変数、型、式など)を表現します。
    • Type: 型情報を表すデータ構造。型の種類(整数、文字列、構造体、ポインタなど)、サイズ、フィールド情報などを保持します。
    • Sym: シンボル(識別子)を表すデータ構造。変数名、関数名、型名などを管理し、それらに関連付けられたNodeTypeへのポインタを持ちます。
    • TFORW: 型が前方参照(forward declaration)されている状態を示す型フラグ。まだ完全な定義が利用できないが、後で解決されることを示すために使用されます。
    • TUNSAFEPTR: unsafe.Pointer型を表す内部定数。
    • incannedimport: コンパイラが「缶詰にされた(canned)」インポート、つまりコンパイラ自体に組み込まれた標準ライブラリのインポートを処理しているかどうかを示すフラグ。

技術的詳細

このコミットの核心は、typedcl2関数のロジックがimporttype関数内に移動されたことです。

typedcl2の役割(移動前): typedcl2は、インポート処理中に型宣言を処理するための関数でした。その主な機能は以下の通りです。

  1. unsafe.Pointerの特別扱い:

    • もしインポート中のパッケージがunsafeであり、宣言されているシンボルがPointerであれば、その型をコンパイラ内部のTUNSAFEPTRunsafe.Pointerを表す型)に強制的に設定します。これは、Goコード内でunsafe.Pointerを直接定義する方法がないため、コンパイラが特別に供給する必要があるためです。
  2. 前方参照型 (TFORW) の解決:

    • pt->etype == TFORWの場合、つまりptが前方参照型である場合、その型定義をtで上書きします。これは、循環参照などによって型が先に参照され、後で定義されるケースに対応するためのものです。
    • copytype(pt->nod, t): ptのノード(AST上の表現)にtの型情報をコピーします。
    • declare(n, PEXTERN): ノードnを外部リンケージを持つものとして宣言します。
    • checkwidth(pt): 型のサイズやアライメントを計算し、検証します。
  3. 型の一貫性チェック:

    • pt->etype != TFORWの場合、つまりptが既に定義されている型である場合、インポートされた型tが既存の型pt->origと一致するかどうかをeqtypeで比較します。
    • もし一致しない場合、yyerror(コンパイラエラー報告関数)を使用して「インポート中に型%Sの定義が不整合です」というエラーを出力します。これは、異なるパッケージ間で同じ名前の型が異なる定義を持っている場合に発生する可能性があります。

importtypeの役割(移動後): importtype関数は、パッケージの型をインポートする際に呼び出される関数です。このコミットにより、typedcl2のすべてのロジックがimporttypeの内部に組み込まれました。

変更後のimporttypeは、pt(既存の型)とt(インポートされる型)が有効な場合に、上記のtypedcl2のロジックをそのまま実行します。これにより、型インポートの処理がexport.cファイル内で完結し、コードの凝集度が高まりました。

この変更は、コンパイラの型システムがインポートされた型をどのように処理し、特にunsafe.Pointerのような特殊な型や前方参照型をどのように解決するかを示す良い例です。コンパイラは、異なるソースから来る型情報の一貫性を保ちつつ、言語のセマンティクス(この場合はunsafe.Pointerの特殊性)を正確に反映させる必要があります。

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

src/cmd/gc/dcl.c (削除)

--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -673,41 +673,6 @@ typedcl1(Node *n, Node *t, int local)
 	return nod(ODCLTYPE, n, N);
 }
 
-/*
- * typedcl1 but during imports
- */
-void
-typedcl2(Type *pt, Type *t)
-{
-	Node *n;
-
-	// override declaration in unsafe.go for Pointer.
-	// there is no way in Go code to define unsafe.Pointer
-	// so we have to supply it.
-	if(incannedimport &&
-	   strcmp(importpkg->name, "unsafe") == 0 &&
-	   strcmp(pt->nod->sym->name, "Pointer") == 0) {
-		t = types[TUNSAFEPTR];
-	}
-
-	if(pt->etype == TFORW)
-		goto ok;
-	if(!eqtype(pt->orig, t))
-		yyerror("inconsistent definition for type %S during import\n\t%lT\n\t%lT", pt->sym, pt->orig, t);
-	return;
-
-ok:
-	n = pt->nod;
-	copytype(pt->nod, t);
-	// unzero nod
-	pt->nod = n;
-
-	pt->sym->lastlineno = parserline();
-	declare(n, PEXTERN);
-
-	checkwidth(pt);
-}
-
 /*
  * structs, functions, and methods.
  * they don't belong here, but where do they belong?

src/cmd/gc/export.c (追加・変更)

--- a/src/cmd/gc/export.c
+++ b/src/cmd/gc/export.c
@@ -349,8 +349,31 @@ importvar(Sym *s, Type *t, int ctxt)
 void
 importtype(Type *pt, Type *t)
 {
-\tif(pt != T && t != T)\n-\t\ttypedcl2(pt, t);\n+\tNode *n;\n+\n+\tif(pt != T && t != T) {\n+\t\t// override declaration in unsafe.go for Pointer.\n+\t\t// there is no way in Go code to define unsafe.Pointer\n+\t\t// so we have to supply it.\n+\t\tif(incannedimport &&\n+\t\t   strcmp(importpkg->name, "unsafe") == 0 &&\n+\t\t   strcmp(pt->nod->sym->name, "Pointer") == 0) {\n+\t\t\tt = types[TUNSAFEPTR];\n+\t\t}\n+\n+\t\tif(pt->etype == TFORW) {\n+\t\t\tn = pt->nod;\n+\t\t\tcopytype(pt->nod, t);\n+\t\t\t// unzero nod\n+\t\t\tpt->nod = n;\n+\t\t\t\n+\t\t\tpt->sym->lastlineno = parserline();\n+\t\t\tdeclare(n, PEXTERN);\n+\t\t\t\n+\t\t\tcheckwidth(pt);\n+\t\t} else if(!eqtype(pt->orig, t))\n+\t\t\tyyerror("inconsistent definition for type %S during import\\n\\t%lT\\n\\t%lT", pt->sym, pt->orig, t);\n+\t}\n \n \tif(debug['E'])\n \t\tprint("import type %T %lT\\n", pt, t);

src/cmd/gc/go.h (削除)

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -964,7 +964,6 @@ Type*	tointerface(NodeList *l);\n Type*	tostruct(NodeList *l);\n Node*	typedcl0(Sym *s);\n Node*	typedcl1(Node *n, Node *t, int local);\n-void	typedcl2(Type *pt, Type *t);\n Node*	typenod(Type *t);\n NodeList*	variter(NodeList *vl, Node *t, NodeList *el);\n 

コアとなるコードの解説

このコミットの主要な変更は、typedcl2関数の本体がsrc/cmd/gc/dcl.cからsrc/cmd/gc/export.c内のimporttype関数にそのまま移動されたことです。

src/cmd/gc/dcl.cからの削除: typedcl2関数は、typedcl1(通常の型宣言)とは異なり、「インポート中」に特化した型宣言処理を行うためのものでした。この関数がdcl.cから完全に削除されたことで、dcl.cはより一般的な宣言処理に集中するようになりました。

src/cmd/gc/export.cへの統合: export.cは、コンパイラがパッケージの型、変数、関数などをエクスポートおよびインポートする際のロジックを扱うファイルです。 importtype関数は、インポートされた型tを、既存の型pt(通常は前方参照型か、既にシンボルテーブルに存在する型)と関連付ける役割を担っています。

変更前は、importtypeが単にtypedcl2(pt, t)を呼び出すだけでした。 変更後は、typedcl2の内部ロジック(unsafe.Pointerの特別処理、前方参照型の解決、型の一貫性チェック)がimporttypeif(pt != T && t != T)ブロック内に直接インライン化されました。

この統合により、型インポートの際に必要なすべての処理がimporttype関数内で完結するようになり、コードの可読性と保守性が向上しました。特に、インポートされた型が前方参照型である場合の解決ロジックや、unsafe.Pointerのような特殊な型の処理が、インポート処理の文脈で直接扱われるようになった点が重要です。

src/cmd/gc/go.hからの宣言削除: typedcl2関数が外部から呼び出される必要がなくなったため、その関数プロトタイプ(宣言)がヘッダーファイルgo.hから削除されました。これは、コンパイラの内部APIを整理し、不要な公開を避けるための標準的なプラクティスです。

全体として、このコミットはGoコンパイラの初期段階における内部構造の洗練と、機能の論理的なグループ化を反映しています。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (GitHub): https://github.com/golang/go
  • Go言語のコードレビューシステム (Gerrit): https://golang.org/cl/5447043 (コミットメッセージに記載されているCLリンク)
  • Goコンパイラの歴史と進化に関する一般的な情報 (Goブログ、カンファレンストークなど)
  • コンパイラ設計に関する一般的な知識 (型システム、AST、シンボルテーブルなど)
  • unsafe.Pointerに関するGoの公式ドキュメントや解説記事