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

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

このコミットは、Go言語のコンパイラ(gc)と特定のアーキテクチャ向けコンパイラ(6g、x86-64向け)に対する変更を含んでいます。主な目的は、Go言語における unsafe パッケージの基盤を構築することです。具体的には、unsafe パッケージの型定義をコンパイラに組み込むためのメカニズムを導入し、関連するビルドプロセスとコード構造を更新しています。

コミット

commit 1d4daa2d3919f3df37c780fca651f23c6762b3e1
Author: Ken Thompson <ken@golang.org>
Date:   Mon Dec 8 19:46:39 2008 -0800

    foundation for import unsafe
    
    R=r
    OCL=20794
    CL=20794

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

https://github.com/golang/go/commit/1d4daa2d3919f3df37c780fca651f23c6762b3e1

元コミット内容

    foundation for import unsafe
    
    R=r
    OCL=20794
    CL=20794

変更の背景

Go言語は、システムプログラミングの領域でも利用されることを想定して設計されました。そのため、低レベルなメモリ操作や、Goの型システムでは表現できないような操作が必要となる場合があります。このような操作を可能にするために、unsafe パッケージの導入が不可欠でした。

このコミットは、unsafe パッケージがGoプログラムから import "unsafe" として利用できるようになるための初期段階の作業です。具体的には、コンパイラが unsafe パッケージの定義(特に unsafe.Pointer 型)を認識し、内部的に利用できるようにするための基盤を整備しています。当時のGo言語はまだ開発の非常に初期段階であり、言語のコア機能や標準ライブラリの設計が活発に行われていました。unsafe パッケージは、Goの型安全性を損なわずに、特定の高度なユースケースに対応するための重要な要素として位置づけられました。

前提知識の解説

  • Go言語のコンパイラ (gc): Go言語の公式コンパイラであり、Goソースコードを機械語に変換する役割を担います。このコミットの時点では、src/cmd/gc ディレクトリにそのソースコードが存在していました。
  • 6g: x86-64アーキテクチャ向けのGoコンパイラです。Goのコンパイラは、ターゲットアーキテクチャごとに異なる名前(例: 6g for amd64, 8g for arm, 5g for arm64など)を持つことがありました。
  • unsafe パッケージ: Go言語において、型安全性をバイパスして低レベルなメモリ操作を可能にするための特別なパッケージです。主に unsafe.Pointer 型を提供し、任意の型のポインタと uintptr(符号なし整数型)の間で変換を行うことができます。これは、C言語との相互運用や、特定のパフォーマンス最適化、あるいはGoランタイム自体の実装などで利用されます。
  • sys パッケージ: このコミット以前から存在していた、Goランタイムの内部で利用される低レベルな関数や型を定義するパッケージです。unsafe パッケージと同様に、コンパイラによって特別に扱われます。
  • mksys ユーティリティ: src/cmd/gc/mksys.c に実装されているC言語のプログラムで、sys.gounsafe.go のようなGoソースファイルから、コンパイラが内部的に利用するC言語のヘッダファイル(sysimport.c など)を生成する役割を担っていました。これは、Goの初期のコンパイラがC言語で書かれていたため、Goの定義をC言語のコードから利用可能にするための仕組みでした。
  • go.y (Yacc/Bison): Goコンパイラのパーサー定義ファイルです。Yacc (Yet Another Compiler Compiler) または Bison は、文法定義からパーサーを生成するためのツールです。go.y はGo言語の構文規則を定義しており、コンパイラがGoソースコードを解析する際に使用されます。
  • any: このコミットの時点での any 型は、現在のGo言語における interface{} 型(任意の型の値を保持できるインターフェース)に相当します。コミット内で the any type is restricted というエラーメッセージが見られることから、any 型の利用には特定の制約があったことが伺えます。

技術的詳細

このコミットの核心は、Goコンパイラが unsafe パッケージの定義を内部的に取り込むための新しいビルドメカニズムとコードパスを導入した点にあります。

  1. mksys ユーティリティの汎用化:

    • 以前は sys.go のみを処理していた mksys が、引数として渡されたパッケージ名(例: sysunsafe)に基づいて、対応するGoソースファイル(例: sys.gounsafe.go)を処理し、C言語のコードを生成するように変更されました。
    • 生成されるCコード内の変数名も、sysimport から unsafeimport のように、パッケージ名に応じて動的に生成されるようになりました。
    • sys.gounsafe.go の内部で package PACKAGE というプレースホルダーを使用し、mksys がこれを実際のパッケージ名に置換することで、単一のテンプレートファイルで複数のパッケージに対応できるようにしています。
  2. unsafe.go の導入と sysimport.c への組み込み:

    • src/cmd/gc/unsafe.go という新しいファイルが追加されました。このファイルは package PACKAGEexport type pointer *any; を含んでいます。これは unsafe.Pointer 型の初期定義です。
    • src/cmd/gc/sysimport.cchar *unsafeimport = ... という新しいC言語の文字列定数が追加されました。この定数には、unsafe.go の内容がコンパイラが解釈できる形式で埋め込まれています。これは、Goコンパイラが起動時に unsafe パッケージの定義を「焼き付けられた」形でロードするためのメカニズムです。
  3. コンパイラの import 処理の拡張:

    • src/cmd/gc/lex.cimportfile 関数が変更され、import "unsafe" が検出された場合に、cannedimports("unsafe.6", unsafeimport) を呼び出すようになりました。
    • cannedimports 関数自体も汎用化され、インポートするファイル名と、その内容を含むC文字列ポインタを引数として受け取るようになりました。これにより、sysunsafe の両方の「組み込みインポート」を同じメカニズムで処理できるようになりました。
  4. uintptr 型名の標準化:

    • src/cmd/6g/align.cuptrint という型名が uintptr に変更されました。これは、Go言語の型名に関する命名規則の標準化の一環と考えられます。uintptr は、ポインタを保持できる符号なし整数型であり、unsafe パッケージと密接に関連します。
  5. any 型の制約:

    • src/cmd/gc/go.yif($1->otype != T && $1->otype->etype == TANY) yyerror("the any type is restricted"); というコードが追加されました。これは、any 型(現在の interface{})の利用に特定の制約を課すもので、おそらく unsafe.Pointer の導入に伴い、型安全性を維持するための措置と考えられます。

これらの変更により、Goコンパイラは unsafe パッケージの存在を認識し、その型定義を内部的に利用できるようになりました。これは、Go言語が低レベルな操作をサポートしつつ、その型システムを維持するための重要な一歩でした。

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

src/cmd/gc/Makefile

--- a/src/cmd/gc/Makefile
+++ b/src/cmd/gc/Makefile
@@ -39,10 +39,13 @@ y.tab.h: $(YFILES)
 y.tab.c: y.tab.h
  	test -f y.tab.c && touch y.tab.c
  
-sysimport.c:	sys.go mksys.c
+sysimport.c:	sys.go unsafe.go mksys.c
  	gcc -o mksys mksys.c
  	6g sys.go
-\t./mksys sys.6 >_sysimport.c && mv _sysimport.c sysimport.c
+\t6g unsafe.go
+\t./mksys sys >_sysimport.c &&\
+\t\t./mksys unsafe >>_sysimport.c &&\
+\t\tmv _sysimport.c sysimport.c
  
 clean:
  	rm -f $(OFILES) *.6 enam.c 6.out a.out y.tab.h y.tab.c $(LIB) _sysimport.c

sysimport.c の生成ルールが変更され、unsafe.go が依存関係に追加され、mksysunsafe パッケージも処理するようになりました。

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -467,6 +467,7 @@ 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;
+extern	char*	unsafeimport;
 EXTERN	char*	filename;	// name to uniqify names
 EXTERN	void	(*dcladj)(Sym*);	// declaration is being exported/packaged
  
@@ -535,7 +536,7 @@ int	yyparse(void);\
 int	mainlex(int, char*[]);
 void	setfilename(char*);\
 void	importfile(Val*);\
-void	cannedimports(void);\
+void	cannedimports(char*, char*);\
 void	unimportfile();
 int32	yylex(void);
 void	lexinit(void);

unsafeimport 変数の追加と、cannedimports 関数のシグネチャ変更。

src/cmd/gc/lex.c

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -218,6 +218,11 @@ importfile(Val *f)\
  		return;
  	}\
  
+\tif(strcmp(f->u.sval->s, \"unsafe\") == 0) {\
+\t\tcannedimports(\"unsafe.6\", unsafeimport);\
+\t\treturn;\
+\t}\
+\
  	if(!findpkg(f->u.sval))\
  	\tfatal(\"can\'t find import: %Z\", f->u.sval);\
  	imp = Bopen(namebuf, OREAD);\
@@ -277,11 +282,8 @@ unimportfile(void)\
  }\
  
  void
-cannedimports(void)\
+cannedimports(char *file, char *cp)\
  {\
-\tchar *file;\
-\
-\tfile = \"sys.6\";\
  	lineno++;		// if sys.6 is included on line 1,\
  	linehist(file, 0);	// the debugger gets confused
  
@@ -290,7 +292,7 @@ cannedimports(void)\
  	curio.peekc = 0;\
  	curio.peekc1 = 0;\
  	curio.infile = file;\
-\tcurio.cp = sysimport;\
+\tcurio.cp = cp;\
  
  	pkgmyname = S;\
  	inimportsys = 1;

import "unsafe" の処理ロジックが追加され、cannedimports が汎用化されました。

src/cmd/gc/mksys.c

--- a/src/cmd/gc/mksys.c
+++ b/src/cmd/gc/mksys.c
@@ -13,15 +13,22 @@
  int
  main(int argc, char **argv)
  {\
+\tchar *name;\
  	FILE *fin;\
-\tchar buf[1024], *p, *q;\
+\tchar buf[1024], initfunc[1024], *p, *q;\
  
  \tif(argc != 2) {\
-\t\tfprintf(stderr, \"usage: mksys sys.6\\n\");
+\t\tfprintf(stderr, \"usage: sys sys\\n\");
+\t\tfprintf(stderr, \"in file $1.6 s/PACKAGE/$1/\\n\");
  \t\texit(1);\
  \t}\
-\tif((fin = fopen(argv[1], \"r\")) == NULL) {\
-\t\tfprintf(stderr, \"open %s: %s\\n\", argv[1], strerror(errno));
+\n+\tname = argv[1];\
+\tsnprintf(initfunc, sizeof(initfunc), \"init_%s_function\", name);\
+\n+\tsnprintf(buf, sizeof(buf), \"%s.6\", name);\
+\tif((fin = fopen(buf, \"r\")) == NULL) {\
+\t\tfprintf(stderr, \"open %s: %s\\n\", buf, strerror(errno));
  \t\texit(1);\
  \t}\
  
@@ -33,7 +40,7 @@ main(int argc, char **argv)\
  \texit(1);\
  
  begin:\
-\tprintf(\"char *sysimport = \\n\");
+\tprintf(\"char *%simport = \\n\", name);\
  
  \t// process imports, stopping at $$ that closes them
  \twhile(fgets(buf, sizeof buf, fin) != NULL) {\
@@ -45,17 +52,21 @@ begin:\
  \t\tfor(p=buf; *p==\' \' || *p == \'\\t\'; p++)\
  \t\t\t;\
  
-\t\t// cut out decl of init_sys_function - it doesn\'t exist
-\t\tif(strstr(buf, \"init_sys_function\"))
+\t\t// cut out decl of init_$1_function - it doesn\'t exist
+\t\tif(strstr(buf, initfunc))\
  \t\t\tcontinue;\
  
-\t\t// sys.go claims to be in package SYS to avoid
-\t\t// conflicts during \"6g sys.go\".  rename SYS to sys.\
-\t\tfor(q=p; *q; q++)\
-\t\t\tif(memcmp(q, \"SYS\", 3) == 0)\
-\t\t\t\tmemmove(q, \"sys\", 3);\
+\t\t// sys.go claims to be in package PACKAGE to avoid
+\t\t// conflicts during \"6g sys.go\".  rename PACKAGE to $2.\
+\t\tprintf(\"\\t\\\"\");\
+\t\twhile(q = strstr(p, \"PACKAGE\")) {\
+\t\t\t*q = 0;\
+\t\t\tprintf(\"%s\", p);\t// up to the substitution
+\t\t\tprintf(\"%s\", name);\t// the sub name
+\t\t\tp = q+7;\t\t// continue with rest
+\t\t}\
  
-\t\tprintf(\"\\t\\\"%s\\\\n\\\"\\n\", p);\
+\t\tprintf(\"%s\\\\n\\\"\\n\", p);\
  \t}\
  \tfprintf(stderr, \"did not find end of imports\\n\");
  \texit(1);\

mksys ユーティリティが汎用化され、引数としてパッケージ名を受け取り、PACKAGE プレースホルダーを置換するようになりました。

src/cmd/gc/sysimport.c

--- a/src/cmd/gc/sysimport.c
+++ b/src/cmd/gc/sysimport.c
@@ -79,3 +79,8 @@ char *sysimport =\
  	\"export func sys.semrelease (sema *int32)\\n\"\
  	\"\\n\"\
  	\"$$\\n\";
+char *unsafeimport = 
+\t\"package unsafe\\n\"\
+\t\"export type unsafe.pointer *any\\n\"\
+\t\"\\n\"\
+\t\"$$\\n\";

unsafeimport という新しいC文字列定数が追加され、unsafe パッケージの初期定義(unsafe.pointer *any)が含まれています。

src/cmd/gc/unsafe.go

--- /dev/null
+++ b/src/cmd/gc/unsafe.go
@@ -0,0 +1,8 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+package PACKAGE
+
+export	type	pointer	*any;

新しく追加されたファイルで、unsafe パッケージのGoソースコードとしての定義を含みます。

コアとなるコードの解説

このコミットの最も重要な変更は、Goコンパイラが unsafe パッケージを「組み込み」で認識し、その定義を利用できるようにした点です。

  1. unsafe.gosysimport.c の連携:

    • unsafe.gounsafe パッケージのGo言語での定義(type pointer *any)を提供します。
    • mksys ユーティリティは、この unsafe.go を読み込み、その内容をC言語の文字列リテラルとして sysimport.c 内の unsafeimport 変数に埋め込みます。
    • これにより、Goコンパイラ(C言語で書かれている部分)は、コンパイル時に unsafe パッケージの定義を直接参照できるようになります。これは、Goの標準ライブラリの一部がGoコンパイラ自体に「焼き付けられている」初期のGoの設計パターンを示しています。
  2. import "unsafe" の処理:

    • Goソースコード内で import "unsafe" が記述されると、コンパイラの字句解析器(lex.c)がこれを検出し、cannedimports("unsafe.6", unsafeimport) を呼び出します。
    • cannedimports 関数は、unsafeimport に格納されたC文字列の内容を、あたかも unsafe.6 というファイルから読み込んだかのように処理します。これにより、unsafe.pointer などの型がコンパイラのシンボルテーブルに登録され、Goコード内で利用可能になります。
  3. mksys の汎用化の意義:

    • mksyssysunsafe の両方を処理できるように汎用化されたことは、Goコンパイラのビルドシステムがより柔軟になったことを意味します。これにより、将来的に他の特別な組み込みパッケージを追加する際にも、同様のメカニズムを再利用できるようになります。package PACKAGE の置換は、この汎用化を実現するための巧妙な方法です。
  4. uintptrany の関連:

    • uintptr は、ポインタの値を整数として扱うための型であり、unsafe.Pointer との間で相互変換が可能です。このコミットで uptrint から uintptr への名称変更が行われたことは、unsafe パッケージの導入と合わせて、低レベルメモリ操作の基盤が整備されつつあったことを示唆しています。
    • any 型(interface{})に対する制約の追加は、unsafe.Pointer が任意の型へのポインタを表現できるようになったことで、型安全性を維持するためのバランスを取る必要があったことを示しています。

これらの変更は、Go言語がその初期段階で、型安全性と低レベルな操作のバランスをどのように取ろうとしていたかを示す貴重な例です。

関連リンク

参考にした情報源リンク

  • Go言語の公式リポジトリ: https://github.com/golang/go
  • Go言語の初期の歴史に関する情報(一般的なGoの歴史サイトやブログ記事など)
  • Yacc/Bisonに関する一般的な情報(パーサー生成の概念理解のため)