[インデックス 167] ファイルの概要
このコミットは、Goコンパイラ(gc
)におけるインポート/エクスポートメカニズムの改善に焦点を当てています。具体的には、パッケージのインポートとエクスポートの処理方法がより堅牢になり、特にパッケージ名の扱いと、コンパイラ内部でのシンボル解決に関する変更が含まれています。src/cmd/gc
ディレクトリ内のコンパイラ関連ファイル(export.c
, go.h
, go.y
, lex.c
, subr.c
)と、浮動小数点リテラルのテストファイル(test/float_lit.go
)が影響を受けています。
コミット
- コミットハッシュ:
343f5aa7b4e11a8c0cb987fd7f7e9cbd66127c04
- Author: Ken Thompson ken@golang.org
- Date: Thu Jun 12 21:48:56 2008 -0700
- コミットメッセージ:
better import/export SVN=122584
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/343f5aa7b4e11a8c0cb987fd7f7e9cbd66127c04
元コミット内容
better import/export
SVN=122584
変更の背景
このコミットは、Go言語の初期段階におけるコンパイラのインポート/エクスポート機能の改善を目的としています。Go言語では、パッケージはコードの再利用とモジュール化の基本単位です。あるパッケージが別のパッケージの機能を利用するためには、そのパッケージをインポートする必要があります。コンパイラは、インポートされたパッケージの公開されたシンボル(関数、変数、型など)を認識し、それらを現在のコンパイル単位で利用できるようにする必要があります。
初期のコンパイラ実装では、インポート/エクスポートのメカニズムがまだ成熟しておらず、特にパッケージ名の解決や、異なるパッケージ間でシンボルがどのように参照されるかについて、改善の余地がありました。この「better import/export」というコミットメッセージは、これらの基本的なコンパイラ機能の堅牢性と正確性を向上させるための取り組みを示唆しています。具体的には、パッケージのインポート時に、そのパッケージが持つ本来の名前と、現在のコンパイル単位でそのパッケージに割り当てられる可能性のある別名(エイリアス)の間の関係をより適切に管理することが求められていました。
また、test/float_lit.go
の変更は、コンパイラの変更とは直接関係ないように見えますが、Go言語の初期開発では、コンパイラの変更と同時に、既存のテストコードの修正や改善が行われることが一般的でした。このテストファイルの変更は、浮動小数点数の比較ロジックの改善であり、コンパイラの変更とは独立した品質向上の一環である可能性が高いです。
前提知識の解説
Go言語のパッケージシステム
Go言語は、コードをパッケージに分割して管理します。各Goファイルは必ずpackage
宣言を持ち、同じパッケージに属するファイルは同じディレクトリに配置されます。他のパッケージの公開された(大文字で始まる)識別子を利用するには、import
文を使ってそのパッケージをインポートする必要があります。
例:
package main
import (
"fmt" // 標準ライブラリのfmtパッケージをインポート
"math" // 標準ライブラリのmathパッケージをインポート
)
func main() {
fmt.Println("Hello, Go!")
fmt.Println(math.Pi)
}
Goコンパイラ (gc
) の構造(初期段階)
Go言語の公式コンパイラは、初期にはgc
(Go Compiler)と呼ばれていました。これは、C言語で書かれたコンパイラであり、伝統的なコンパイラのフェーズ(字句解析、構文解析、意味解析、コード生成など)を持っていました。
- 字句解析 (Lexical Analysis): ソースコードをトークン(キーワード、識別子、演算子など)のストリームに変換します。
lex.c
がこの役割を担います。 - 構文解析 (Syntax Analysis): トークンのストリームを解析し、プログラムの構造を抽象構文木(AST)として構築します。
go.y
はYacc/Bisonの入力ファイルであり、Go言語の文法規則を定義し、構文解析器を生成します。 - 意味解析 (Semantic Analysis): ASTを走査し、型チェック、シンボル解決、スコープ解決などを行います。
- エクスポート/インポート (Export/Import): コンパイラは、コンパイルされたパッケージの公開されたシンボル情報をエクスポートファイル(通常は
.a
アーカイブ内に埋め込まれる)として出力します。他のパッケージがそのパッケージをインポートする際には、このエクスポートファイルを読み込み、シンボル情報をインポートします。export.c
がこの機能に関連します。 - シンボル (Sym): コンパイラ内部で、変数、関数、型などの識別子を表すデータ構造です。シンボルは、その名前、型、スコープ、所属するパッケージなどの情報を含みます。
Yacc/Bison
go.y
ファイルは、Yacc(Yet Another Compiler Compiler)またはそのGNU版であるBisonの入力ファイルです。これらのツールは、文法規則(BNF形式)から構文解析器(パーサー)を自動生成します。.y
ファイルには、文法規則と、各規則がマッチしたときに実行されるC言語のアクションコードが記述されます。
Node
とSym
Goコンパイラの内部では、Node
は抽象構文木(AST)のノードを表す構造体です。Sym
はシンボルテーブルのエントリを表す構造体で、識別子に関する情報(名前、型、スコープなど)を保持します。Node
はSym
へのポインタを持つことがあり、これによりASTのノードが特定のシンボルに関連付けられます。
技術的詳細
このコミットの主要な変更点は、Goコンパイラにおけるパッケージのインポートとエクスポートの処理、特にパッケージ名の解決ロジックの改善にあります。
src/cmd/gc/export.c
の変更
-
dumpexport
関数の変更:- エクスポートされる情報に、現在のパッケージ名を追加する行が追加されました:
Bprint(bout, " package %s\\n", package);
- これにより、インポート側がエクスポートされたパッケージの本来の名前を正確に知ることができるようになります。これは、パッケージのエイリアス(別名)が使用される場合に特に重要です。
- エクスポートされる情報に、現在のパッケージ名を追加する行が追加されました:
-
renamepkg
関数の追加:- 新しい関数
renamepkg(Node *n)
が追加されました。この関数は、インポートされたシンボル(Node *n
)のパッケージシンボル(n->psym
)を、必要に応じてリネームする役割を担います。 - 具体的には、
n->psym
がpkgimportname
(インポートされたパッケージの本来の名前)と一致し、かつpkgmyname
(現在のコンパイル単位でそのパッケージに割り当てられた別名)が設定されている場合、n->psym
をpkgmyname
に置き換えます。 - また、
n->psym->lexical
がLPACK
(パッケージを表す字句)でない場合に警告を発し、LPACK
に設定するロジックも含まれています。これは、シンボルがパッケージとして正しく認識されることを保証するためです。
- 新しい関数
-
getimportsym
関数の変更:- 以前のバージョンでは、
pkgmyname
が設定されている場合にpkg
変数をpkgmyname->name
で上書きしていましたが、このロジックが削除されました。 - これは、
renamepkg
関数が導入されたことで、パッケージのリネーム処理がより集中的かつ正確に行われるようになったため、getimportsym
内での直接的なpkg
名の上書きが不要になったことを示唆しています。getimportsym
は、インポートされたシンボルをルックアップする際に、そのシンボルが属するパッケージの本来の名前を使用し、リネームは後続のrenamepkg
で行うという役割分担が明確化されました。
- 以前のバージョンでは、
src/cmd/gc/go.h
の変更
pkgimportname
の追加:EXTERN Sym* pkgimportname; // package name from imported package
- インポートされたパッケージの本来の名前を保持するための新しいグローバルシンボル
pkgimportname
が追加されました。これは、renamepkg
関数でパッケージのリネームを処理する際に使用されます。
renamepkg
関数のプロトタイプ宣言の追加:void renamepkg(Node*);
export.c
で定義されたrenamepkg
関数のプロトタイプがヘッダーファイルに追加され、他のファイルから利用可能になりました。
src/cmd/gc/go.y
の変更
-
import_stmt
文法の変更:import_stmt: import_here import_there
からimport_stmt: import_here import_package import_there
に変更されました。- これにより、
import_package
という新しい文法規則がインポート文の解析フローに組み込まれました。
-
import_package
文法規則の追加:- 新しい文法規則
import_package
が追加されました。これは、LPACKAGE sym
という形式で、インポートされたパッケージの本来の名前(sym
)を解析します。 - 解析されたシンボルは
pkgimportname
グローバル変数に格納されます。 pkgmyname
(現在のパッケージでの別名)が設定されていない場合、pkgimportname->lexical
がLPACK
に設定されます。これは、インポートされたパッケージが正しくパッケージとして識別されるようにするためです。
- 新しい文法規則
-
isym
文法規則の変更:isym
は、sym '.' sym
または'(' sym ')' sym '.' sym
の形式で、パッケージ修飾されたシンボル(例:fmt.Println
)を解析します。- 以前は
$1->lexical = LPACK;
という行がありましたが、これは削除されました。 - 代わりに、解析された
Node
に対してrenamepkg($$);
が呼び出されるようになりました。これにより、パッケージ修飾されたシンボルが解析された直後に、renamepkg
関数によってパッケージ名のリネーム処理が適用されるようになりました。
-
hidden_importsym
文法規則の変更:'!' isym
の形式で、isym
が解析された後、$$->etype = 1;
という行が追加されました。このetype
の具体的な意味は、このコミットの範囲外ですが、おそらく内部的な型情報やフラグの設定に関連していると考えられます。
src/cmd/gc/lex.c
の変更
- キーワード
sys
の追加:"sys", LPACK, Txxx,
sys
という新しいキーワードが字句解析器に追加され、その字句タイプがLPACK
(パッケージ)として定義されました。このキーワードが具体的にどのように使用されるかは、このコミットの範囲では明確ではありませんが、コンパイラ内部の特殊なパッケージ参照や、将来の言語機能のための予約語である可能性があります。
src/cmd/gc/subr.c
の変更
TMAP
の表示形式の変更:case TMAP:
のsnprint
関数呼び出しで、"[%T]%T"
から"MAP[%T]%T"
に変更されました。- これは、マップ型の文字列表現をより明確にするための変更であり、デバッグ出力やエラーメッセージなどでマップ型が表示される際に、
MAP
というプレフィックスが付くようになります。コンパイラのインポート/エクスポート機能とは直接関係ありませんが、コンパイラの出力の可読性を向上させるための改善です。
test/float_lit.go
の変更
- 浮動小数点数比較ロジックの改善:
db = db*pow10(pow);
がdb *= pow10(pow);
に変更されました。これは単なる短縮記法への変更です。- 浮動小数点数の近似比較を行う
close
関数において、絶対誤差と相対誤差の計算方法が変更されました。 - 以前は
dd := da-db;
とif de*1.0e-14 > dd
のように絶対誤差と相対誤差を組み合わせていましたが、新しいロジックではde := (da-db) /da;
とif de < 1.0e-14
のように、主に相対誤差(de
)が非常に小さいかどうかで比較を行うようになりました。 - これは、浮動小数点数の比較における一般的なプラクティスであり、絶対誤差と相対誤差を適切に組み合わせることで、非常に小さい値や非常に大きい値の比較でも頑健な結果を得ることを目指しています。この変更は、コンパイラの機能とは直接関係なく、テストの正確性を向上させるためのものです。
コアとなるコードの変更箇所
src/cmd/gc/export.c
--- a/src/cmd/gc/export.c
+++ b/src/cmd/gc/export.c
@@ -252,6 +254,8 @@ dumpexport(void)
Bprint(bout, " import\\n");
Bprint(bout, " ((\\n");
+ Bprint(bout, " package %s\\n", package);
+
// print it depth first
for(d=exportlist->forw; d!=D; d=d->forw) {
dynlineno = d->lineno;
@@ -266,6 +270,19 @@ dumpexport(void)
/*
* ******* import *******
*/
+void
+renamepkg(Node *n)
+{
+ if(n->psym == pkgimportname)
+ if(pkgmyname != S)
+ n->psym = pkgmyname;
+
+ if(n->psym->lexical != LPACK) {
+ warn("%S is becoming a package behind your back", n->psym);
+ n->psym->lexical = LPACK;
+ }
+}
+
Sym*
getimportsym(Node *ss)
{
@@ -276,9 +293,6 @@ getimportsym(Node *ss)
fatal("getimportsym: oops1 %N\\n", ss);
pkg = ss->psym->name;
- if(pkgmyname != S)
- pkg = pkgmyname->name;
-
s = pkglookup(ss->sym->name, pkg);
/* botch - need some diagnostic checking for the following assignment */
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -333,6 +333,7 @@ EXTERN Sym* hash[NHASH];
EXTERN Sym* dclstack;
EXTERN Sym* b0stack;
EXTERN Sym* pkgmyname; // my name for package
+EXTERN Sym* pkgimportname; // package name from imported package
EXTERN int tptr; // either TPTR32 or TPTR64
extern char* sysimport;
@@ -498,6 +499,7 @@ Type* forwdcl(Sym*);\n /*
*\texport.c
*/
+void renamepkg(Node*);\n void markexport(Node*);\n void dumpe(Sym*);\n void dumpexport(void);\n```
### `src/cmd/gc/go.y`
```diff
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -86,7 +86,7 @@ import:
|\tLIMPORT '(' import_stmt_list_r osemi ')'
import_stmt:
-\timport_here import_there
+\timport_here import_package import_there
import_here:
LLITERAL
@@ -109,6 +109,17 @@ import_here:
importfile(&$2);
}
+import_package:
+ LPACKAGE sym
+ {
+ pkgimportname = $2;
+
+ // if we are not remapping the package name
+ // then the imported package name is LPACK
+ if(pkgmyname == S)
+ pkgimportname->lexical = LPACK;
+ }
+
import_there:
hidden_import_list_r ')' ')'
{
@@ -1330,11 +1341,11 @@ hidden_import:
isym:
sym '.' sym
{
-\t\t$1->lexical = LPACK;
$$ = nod(OIMPORT, N, N);\
$$->osym = $1;\
$$->psym = $1;\
$$->sym = $3;\
+\t\trenamepkg($$);\
}
|\t'(' sym ')' sym '.' sym
{
@@ -1342,6 +1353,7 @@ isym:\
$$->osym = $2;\
$$->psym = $4;\
$$->sym = $6;\
+\t\trenamepkg($$);\
}\n```
## コアとなるコードの解説
このコミットの核心は、Goコンパイラがパッケージのインポートとエクスポートをどのように処理するか、特にパッケージ名の解決とシンボル参照のメカニズムを改善することにあります。
1. **エクスポートされる情報へのパッケージ名の追加 (`export.c`)**:
`dumpexport`関数は、コンパイルされたパッケージの公開シンボル情報をエクスポートファイルに書き出す役割を担っています。この変更により、エクスポートされる情報の中に、そのパッケージの本来の名前(`package`変数で表される)が明示的に含まれるようになりました。
```c
Bprint(bout, " package %s\\n", package);
```
これは、インポート側がパッケージをインポートする際に、そのパッケージが持つ「真の名前」を知るために不可欠です。Goでは、`import foo "bar/baz"`のようにパッケージに別名(エイリアス)を付けることができますが、コンパイラ内部ではそのパッケージの本来の名前も追跡する必要があります。この変更は、そのための基盤を強化します。
2. **`renamepkg`関数の導入と役割 (`export.c`, `go.h`, `go.y`)**:
`renamepkg`関数は、インポートされたシンボル(`Node *n`)のパッケージシンボル(`n->psym`)を、必要に応じてリネームする新しいメカニズムを提供します。
```c
void
renamepkg(Node *n)
{
if(n->psym == pkgimportname)
if(pkgmyname != S)
n->psym = pkgmyname;
if(n->psym->lexical != LPACK) {
warn("%S is becoming a package behind your back", n->psym);
n->psym->lexical = LPACK;
}
}
```
* `pkgimportname`は、`go.y`で解析されたインポートされたパッケージの本来の名前を保持する新しいグローバルシンボルです。
* `pkgmyname`は、現在のコンパイル単位でそのパッケージに割り当てられた別名(エイリアス)を保持する既存のグローバルシンボルです。
* この関数は、インポートされたシンボルが`pkgimportname`と一致し、かつ`pkgmyname`が設定されている場合(つまり、パッケージに別名が付けられている場合)、そのシンボルのパッケージ参照を`pkgmyname`に置き換えます。これにより、コンパイラはコード内で使用されているエイリアスを正しく解決できるようになります。
* また、シンボルがパッケージとして正しく認識されていない場合に警告を発し、`LPACK`(パッケージを表す字句)としてマークすることで、コンパイラの内部状態の一貫性を保ちます。
`go.y`の`isym`(パッケージ修飾されたシンボル、例: `fmt.Println`)の解析ルールに`renamepkg($$);`が追加されたことで、このようなシンボルが構文解析された直後に、パッケージ名のリネーム処理が適用されるようになりました。これにより、シンボル解決の段階で正しいパッケージ名が使用されることが保証されます。
3. **`pkgimportname`グローバルシンボルの追加 (`go.h`)**:
`go.h`に`EXTERN Sym* pkgimportname;`が追加されたことで、インポートされたパッケージの本来の名前をコンパイラ全体で共有できるようになりました。これは`renamepkg`関数が正しく機能するために不可欠です。
4. **`import_package`文法規則の導入 (`go.y`)**:
`go.y`の文法に`import_package`という新しい規則が追加されました。
```yacc
import_package:
LPACKAGE sym
{
pkgimportname = $2;
// if we are not remapping the package name
// then the imported package name is LPACK
if(pkgmyname == S)
pkgimportname->lexical = LPACK;
}
```
この規則は、インポート文の中でパッケージの本来の名前を解析し、それを`pkgimportname`に格納します。これにより、コンパイラはインポートされたパッケージの本来の名前を正確に把握し、その後のリネーム処理に利用できるようになります。
これらの変更は、Goコンパイラがパッケージのインポートとエクスポートをより正確かつ柔軟に処理するための重要なステップであり、特にパッケージのエイリアスや、異なるパッケージ間でのシンボル参照の堅牢性を向上させることに貢献しています。
## 関連リンク
* Go言語の仕様 (The Go Programming Language Specification): [https://go.dev/ref/spec](https://go.dev/ref/spec)
* Goコンパイラの歴史と進化に関する情報 (Go compiler history and evolution): [https://go.dev/doc/go1.html](https://go.dev/doc/go1.html) (Go 1のリリースノートなど、初期のGoに関する情報源)
* Yacc/Bisonのドキュメント (Yacc/Bison documentation): [https://www.gnu.org/software/bison/manual/](https://www.gnu.org/software/bison/manual/)
## 参考にした情報源リンク
* Go言語の公式ドキュメント
* Go言語のソースコード (特に`src/cmd/gc`ディレクトリ)
* Yacc/Bisonに関する一般的な情報
* 浮動小数点数の比較に関する一般的な情報 (IEEE 754など)
* [https://github.com/golang/go/commit/343f5aa7b4e11a8c0cb987fd7f7e9cbd66127c04](https://github.com/golang/go/commit/343f5aa7b4e11a8c0cb987fd7f7e9cbd66127c04) (このコミットのGitHubページ)
* [https://go.dev/doc/go1.html](https://go.dev/doc/go1.html) (Go 1のリリースノート)
* [https://go.dev/ref/spec](https://go.dev/ref/spec) (Go言語の仕様)
* [https://www.gnu.org/software/bison/manual/](https://www.gnu.org/software/bison/manual/) (Bisonマニュアル)
* [https://en.wikipedia.org/wiki/IEEE_754](https://en.wikipedia.org/wiki/IEEE_754) (IEEE 754 浮動小数点数標準)
* [https://floating-point-gui.de/](https://floating-point-gui.de/) (浮動小数点数に関するガイド)
* [https://www.cs.cmu.edu/~fp/courses/15213-s04/lectures/L06-FloatingPoint.pdf](https://www.cs.cmu.edu/~fp/courses/15213-s04/lectures/L06-FloatingPoint.pdf) (浮動小数点数に関する講義資料)
* [https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/) (浮動小数点数の比較に関する記事)