[インデックス 1464] ファイルの概要
このコミットは、Go言語のランタイムコードをGCCコンパイラでより容易にコンパイルできるようにするための調整を含んでいます。具体的には、非ASCII文字を含むシンボル名のマクロによる置換、符号なし整数との比較に関連する変数の型変更、およびvoid*
ポインタをより具体的なMLink*
型に修正する変更が行われています。これらの変更は、Goの初期開発段階におけるコンパイラ互換性の問題を解決し、コードの堅牢性と可読性を向上させることを目的としています。
コミット
commit 9b8da82d72fa41452c6640fce33a80414f48cfca
Author: Ian Lance Taylor <iant@golang.org>
Date: Tue Jan 13 09:55:24 2009 -0800
Tweak code to make it easier to compile with gcc.
+ Use macros to name symbols with non-ASCII characters.
+ Make some variables unsigned, because they are compared
against unsigned values.
+ Fix a few void* pointers to be MLink*.
R=rsc
DELTA=94 (44 added, 3 deleted, 47 changed)
OCL=22303
CL=22638
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9b8da82d72fa41452c6640fce33a80414f48cfca
元コミット内容
このコミットは、Go言語のランタイムコードがGCCでコンパイルされる際の互換性を向上させるためのコード調整を行います。主な変更点は以下の通りです。
- 非ASCII文字を含むシンボル名のマクロ化:
sys·
のような非ASCII文字(中点)を含むシンボル名を、GCCでコンパイル可能にするためにマクロを使用してsys_
のようなASCII文字のみのシンボル名に置き換えます。 - 変数の符号なし型への変更: 符号なしの値と比較される一部の変数を
unsigned
型に変更し、型不一致による警告や潜在的なバグを回避します。 void*
ポインタのMLink*
への修正: 汎用ポインタであるvoid*
を、メモリ管理のリンクリストで使用されるMLink*
というより具体的な型に修正し、コードの型安全性を向上させます。
変更の背景
Go言語は、その初期段階において、ランタイムの大部分がC言語で記述されており、GCCなどの既存のCコンパイラを使用してコンパイルされていました。このコミットが行われた2009年1月は、Go言語がまだ一般に公開される前の非常に初期の段階であり、コンパイラツールチェーンの成熟度が現在のようには高くありませんでした。
当時のGCCは、識別子に非ASCII文字(特に·
のような特殊文字)を使用することに対して、現在ほど寛容ではなかった可能性があります。Goのランタイムコードでは、システムコールや内部関数にsys·memclr
のような命名規則が用いられていましたが、これがGCCでのコンパイルエラーや警告の原因となっていたと考えられます。
また、C言語における符号付き整数と符号なし整数の比較は、予期せぬ結果を招く可能性がある既知の問題です。特に、符号なし整数は常に非負であるため、符号付き整数との比較では、符号付き整数が負の値であっても符号なし整数に昇格されて比較されるため、論理的な誤りにつながることがあります。このコミットは、このような潜在的な問題を解消し、より堅牢なコードベースを構築することを目指しています。
void*
の使用はC言語では一般的ですが、型安全性の観点からは推奨されません。特定のデータ構造(この場合はメモリ管理のリンクリスト)を扱うポインタがvoid*
として宣言されていると、コンパイラが型チェックを十分に行えず、誤った型のデータが渡された場合に実行時エラーにつながる可能性があります。MLink*
のような具体的な型に修正することで、コンパイラによる厳密な型チェックが可能になり、開発時のエラー発見に役立ちます。
これらの変更は、Goランタイムの基盤を安定させ、異なるコンパイラ環境での互換性を確保し、将来のGo言語の発展のための強固な土台を築く上で重要なステップでした。
前提知識の解説
1. GCCとGoの初期コンパイル環境
Go言語は、Googleによって開発されたプログラミング言語です。その初期のバージョンでは、ランタイム(Goプログラムの実行を支える低レベルなコード群)の多くがC言語で書かれていました。これは、Goコンパイラ自体がまだ完全に自己ホスト型ではなかったため、既存のCコンパイラ(主にGCC)に依存してランタイムをビルドする必要があったためです。
2. 非ASCII文字の識別子とC言語
C言語の標準では、識別子(変数名、関数名など)に使用できる文字に制限があります。伝統的に、識別子は英数字とアンダースコア(_
)で構成されることが一般的でした。しかし、C99標準以降、Unicode文字を識別子に含めることが可能になりました。ただし、これはコンパイラの実装に依存する部分が大きく、特に古いGCCのバージョンでは、·
(中点)のような非ASCII文字が識別子に含まれていると、コンパイルエラーや警告が発生する可能性がありました。
このコミットでは、Goランタイムの内部でsys·memclr
のように中点(·
)を含む関数名が使われていました。これはGoの初期のコンパイラ(6g
, 8g
など)が独自に解釈するシンボル命名規則の一部だった可能性がありますが、GCCでは問題となるため、sys_memclr
のようにアンダースコア(_
)に置き換える必要がありました。
3. 符号付き/符号なし整数の比較
C言語では、整数型にはint
(符号付き整数)とunsigned int
(符号なし整数)があります。これらを比較する際、C言語の標準では「通常の算術変換 (usual arithmetic conversions)」というルールが適用されます。このルールの一つに、一方のオペランドが符号なし型で、もう一方が符号付き型の場合、符号付きオペランドが符号なし型に変換されてから比較が行われるというものがあります。
例えば、int a = -1; unsigned int b = 1;
の場合、a < b
は true
になることを期待しますが、a
が unsigned int
に変換されると、そのビットパターンが非常に大きな正の数として解釈されるため、a < b
は false
になってしまうことがあります。このような挙動は、特にループカウンタや配列のインデックスなどで予期せぬバグを引き起こす可能性があります。
4. void*
と型安全性
void*
はC言語における汎用ポインタ型です。あらゆる型のデータへのポインタを保持できますが、その指し示すデータの型に関する情報を持たないため、逆参照する際には明示的な型キャストが必要です。これは非常に柔軟ですが、同時に型安全性を損なう可能性があります。
例えば、void*
として宣言されたポインタに誤った型のデータへのアドレスが代入されても、コンパイラは警告を発しません。その結果、実行時に誤った型のデータとしてアクセスされ、クラッシュや未定義動作につながる可能性があります。特定のデータ構造(例: リンクリストのノード)を指すことが分かっている場合は、MLink*
のような具体的な型を使用することで、コンパイラがより厳密な型チェックを行い、開発段階でエラーを発見しやすくなります。
5. Goランタイム
Goランタイムは、Goプログラムの実行を管理する低レベルなコードの集合体です。これには、ガベージコレクション、スケジューラ、メモリ割り当て、システムコールラッパーなどが含まれます。Goプログラムが実行される際、これらのランタイム機能がOSと連携し、プログラムの動作を支えます。初期のGoランタイムはC言語で書かれていましたが、後にGo言語自体で書き直されていきました。
技術的詳細
このコミットは、GoランタイムのCコードにおけるGCCとの互換性問題を解決するために、以下の3つの主要な技術的変更を導入しています。
1. 非ASCII文字シンボル名のマクロによる置換
Goの初期ランタイムでは、sys·memclr
やmalloc·Alloc
のように、関数名や変数名に中点(·
)を含む命名規則が採用されていました。この中点はASCII文字セットには含まれないため、当時のGCCコンパイラがこれを識別子として適切に処理できない問題がありました。
この問題を解決するため、コミットではsrc/runtime/runtime.h
に以下の形式のマクロ定義を追加しています。
#ifndef __GNUC__
#define sys_exit sys·exit
#define sys_gosched sys·gosched
// ... 他の多くのシンボル
#endif
この#ifndef __GNUC__
プリプロセッサディレクティブは、「GCCコンパイラでない場合」に続くマクロ定義を有効にするという意味です。これにより、GCC以外のコンパイラ(例えば、Go自身のコンパイラツールチェーン)でコンパイルする際には、sys_exit
というコードがsys·exit
として解釈されます。
一方で、GCCでコンパイルする際には、__GNUC__
が定義されているため、これらのマクロは無視されます。その代わり、コード内のsys·exit
のような呼び出しは、直接sys_exit
に書き換えられています。
例えば、src/runtime/malloc.c
では、sys·memclr
の呼び出しがsys_memclr
に、sys·mmap
の呼び出しがsys_mmap
に直接変更されています。
--- a/src/runtime/malloc.c
+++ b/src/runtime/malloc.c
@@ -83,7 +83,7 @@ free(void *v)
if(sizeclass == 0) {
// Large object.
mstats.alloc -= s->npages<<PageShift;
- sys·memclr(v, s->npages<<PageShift);
+ sys_memclr(v, s->npages<<PageShift);
MHeap_Free(&mheap, s);
return;
}
そして、src/runtime/runtime.h
内の関数宣言も、sys·goexit
からsys_goexit
のように変更されています。
--- a/src/runtime/runtime.h
+++ b/src/runtime/runtime.h
@@ -343,44 +343,80 @@ void noteclear(Note*);
void notesleep(Note*);
void notewakeup(Note*);
+/*
+ * Redefine methods for the benefit of gcc, which does not support
+ * UTF-8 characters in identifiers.
+ */
+#ifndef __GNUC__
+#define sys_exit sys·exit
+// ...
+#endif
+
/*
* low level go -called
*/
-void sys·goexit(void);
-void sys·gosched(void);
-void sys·exit(int32);
-void sys·write(int32, void*, int32);
-void sys·breakpoint(void);
-uint8* sys·mmap(byte*, uint32, int32, int32, int32, uint32);
-void sys·memclr(byte*, uint32);
-void sys·setcallerpc(void*, void*);
-void* sys·getcallerpc(void*);
+void sys_goexit(void);
+void sys_gosched(void);
+void sys_exit(int32);
+void sys_write(int32, void*, int32);
+void sys_breakpoint(void);
+uint8* sys_mmap(byte*, uint32, int32, int32, int32, uint32);
+void sys_memclr(byte*, uint32);
+void sys_setcallerpc(void*, void*);
+void* sys_getcallerpc(void*);
このアプローチにより、単一のソースコードベースで、GCCとGo独自のコンパイラの両方に対応できるようになっています。
2. 変数の符号なし型への変更
src/runtime/malloc.c
とsrc/runtime/msize.c
では、一部のループカウンタやインデックス変数がint32
からuint32
に変更されています。
--- a/src/runtime/malloc.c
+++ b/src/runtime/malloc.c
@@ -195,7 +195,7 @@ mal(uint32 n)
if(0) {
byte *p;
- int32 i;
+ uint32 i;
p = v;
for(i=0; i<n; i++) {
if(p[i] != 0) {
--- a/src/runtime/msize.c
+++ b/src/runtime/msize.c
@@ -57,7 +57,8 @@ SizeToClass(int32 size)
void
InitSizes(void)
{
- int32 align, sizeclass, size, i, nextsize, n;
+ int32 align, sizeclass, size, nextsize, n;
+ uint32 i;
uintptr allocsize, npages;
これは、これらの変数が符号なしの値(例えば、サイズやインデックス)と比較される際に、C言語の「通常の算術変換」ルールによって発生する可能性のある警告や、論理的な誤りを回避するためです。uint32
にすることで、変数が常に非負の値として扱われることが保証され、コードの意図がより明確になり、潜在的なバグが減少します。
3. void*
ポインタのMLink*
への修正
src/runtime/malloc.h
とsrc/runtime/mcentral.c
では、メモリ管理に関連するリンクリストのポインタがvoid*
からMLink*
に変更されています。
--- a/src/runtime/malloc.h
+++ b/src/runtime/malloc.h
@@ -208,7 +208,7 @@ struct MSpan
MSpan *prev; // in a span linked list
PageID start; // starting page number
uintptr npages; // number of pages in span
- void *freelist; // list of free objects
+ MLink *freelist; // list of free objects
uint32 ref; // number of allocated objects in this span
uint32 sizeclass; // size class
uint32 state; // MSpanInUse or MSpanFree
--- a/src/runtime/mcentral.c
+++ b/src/runtime/mcentral.c
@@ -92,7 +92,7 @@ MCentral_Alloc(MCentral *c)
// The objects are linked together by their first words.
// On return, *pstart points at the first object and *pend at the last.
void
-MCentral_FreeList(MCentral *c, int32 n, void *start)
+MCentral_FreeList(MCentral *c, int32 n, MLink *start)
{
MLink *v, *next;
MLink
は、Goランタイムのメモリ管理において、解放されたオブジェクトを連結するためのリンクリストのノードとして使用される構造体です。void*
を使用すると、コンパイラはfreelist
が指すデータの型を認識できず、誤った操作が行われる可能性があります。MLink*
に明示的に型を指定することで、コンパイラはより厳密な型チェックを実行でき、コードの型安全性が向上し、デバッグが容易になります。これは、メモリ管理のようなクリティカルな部分において特に重要です。
これらの変更は、GoランタイムのCコードの品質と移植性を向上させるための、初期段階における重要な改善点を示しています。
コアとなるコードの変更箇所
1. 非ASCII文字シンボル名のマクロ化と使用箇所の変更
src/runtime/runtime.h
でのマクロ定義の追加:
--- a/src/runtime/runtime.h
+++ b/src/runtime/runtime.h
@@ -343,44 +343,80 @@ void noteclear(Note*);
void notesleep(Note*);
void notewakeup(Note*);
+/*
+ * Redefine methods for the benefit of gcc, which does not support
+ * UTF-8 characters in identifiers.
+ */
+#ifndef __GNUC__
+#define sys_exit sys·exit
+#define sys_gosched sys·gosched
+#define sys_memclr sys·memclr
+#define sys_write sys·write
+#define sys_breakpoint sys·breakpoint
+#define sys_bytestorune sys·bytestorune
+#define sys_catstring sys·catstring
+#define sys_cmpstring sys·cmpstring
+#define sys_getcallerpc sys·getcallerpc
+#define sys_goexit sys·goexit
+#define sys_indexstring sys·indexstring
+#define sys_intstring sys·intstring
+#define sys_mal sys·mal
+#define sys_mmap sys·mmap
+#define sys_printarray sys·printarray
+#define sys_printbool sys·printbool
+#define sys_printfloat sys·printfloat
+#define sys_printhex sys·printhex
+#define sys_printint sys·printint
+#define sys_printpc sys·printpc
+#define sys_printpointer sys·printpointer
+#define sys_printstring sys·printstring
+#define sys_printuint sys·printuint
+#define sys_readfile sys·readfile
+#define sys_semacquire sys·semacquire
+#define sys_semrelease sys·semrelease
+#define sys_setcallerpc sys·setcallerpc
+#define sys_slicestring sys·slicestring
+#define sys_stringtorune sys·stringtorune
+#endif
+
/*
* low level go -called
*/
-void sys·goexit(void);
-void sys·gosched(void);
-void sys·exit(int32);
-void sys·write(int32, void*, int32);
-void sys·breakpoint(void);
-uint8* sys·mmap(byte*, uint32, int32, int32, int32, uint32);
-void sys·memclr(byte*, uint32);
-void sys·setcallerpc(void*, void*);
-void* sys·getcallerpc(void*);
+void sys_goexit(void);
+void sys_gosched(void);
+void sys_exit(int32);
+void sys_write(int32, void*, int32);
+void sys_breakpoint(void);
+uint8* sys_mmap(byte*, uint32, int32, int32, int32, uint32);
+void sys_memclr(byte*, uint32);
+void sys_setcallerpc(void*, void*);
+void* sys_getcallerpc(void*);
src/runtime/malloc.c
でのsys·memclr
の呼び出し箇所の変更:
--- a/src/runtime/malloc.c
+++ b/src/runtime/malloc.c
@@ -83,7 +83,7 @@ free(void *v)
if(sizeclass == 0) {
// Large object.
mstats.alloc -= s->npages<<PageShift;
- sys·memclr(v, s->npages<<PageShift);
+ sys_memclr(v, s->npages<<PageShift);
MHeap_Free(&mheap, s);
return;
}
2. 変数の符号なし型への変更
src/runtime/malloc.c
でのi
の型変更:
--- a/src/runtime/malloc.c
+++ b/src/runtime/malloc.c
@@ -195,7 +195,7 @@ mal(uint32 n)
if(0) {
byte *p;
- int32 i;
+ uint32 i;
p = v;
for(i=0; i<n; i++) {
if(p[i] != 0) {
3. void*
ポインタのMLink*
への修正
src/runtime/malloc.h
でのfreelist
の型変更:
--- a/src/runtime/malloc.h
+++ b/src/runtime/malloc.h
@@ -208,7 +208,7 @@ struct MSpan
MSpan *prev; // in a span linked list
PageID start; // starting page number
uintptr npages; // number of pages in span
- void *freelist; // list of free objects
+ MLink *freelist; // list of free objects
uint32 ref; // number of allocated objects in this span
uint32 sizeclass; // size class
uint32 state; // MSpanInUse or MSpanFree
コアとなるコードの解説
1. 非ASCII文字シンボル名のマクロ化と使用箇所の変更
src/runtime/runtime.h
に追加されたマクロは、Goランタイムが複数のコンパイラ環境でビルドされることを可能にするための巧妙な解決策です。
#ifndef __GNUC__
: このプリプロセッサディレクティブは、コンパイル時に__GNUC__
マクロが定義されているかどうかをチェックします。__GNUC__
はGCCコンパイラによって自動的に定義されるマクロです。したがって、このブロック内のコードは「GCC以外のコンパイラでコンパイルされている場合」にのみ有効になります。#define sys_exit sys·exit
: この行は、sys_exit
という識別子をsys·exit
に置き換えるマクロを定義しています。Goの初期のコンパイラは、sys·exit
のような中点を含むシンボル名を生成・認識していました。GCC以外のコンパイラでビルドする際には、ソースコード内のsys_exit
という記述が、Goコンパイラが期待するsys·exit
というシンボル名に変換されます。- 関数宣言の変更:
void sys·goexit(void);
からvoid sys_goexit(void);
への変更は、GCCが非ASCII文字を識別子として扱えない問題に対応するためです。GCCでコンパイルする際には、マクロが有効にならないため、ソースコード自体がGCCが認識できるsys_
形式の識別子を使用する必要があります。
これにより、Goのソースコードは、Go独自のツールチェーンとGCCの両方でコンパイルできる「ユニバーサルな」形式を維持しています。
2. 変数の符号なし型への変更
src/runtime/malloc.c
のmal
関数内のi
変数の型がint32
からuint32
に変更された例は、C言語における符号付き/符号なし整数の比較の潜在的な問題を回避するための典型的な修正です。
// 変更前
// int32 i;
// for(i=0; i<n; i++) { ... }
// 変更後
uint32 i;
for(i=0; i<n; i++) { ... }
このi
はループカウンタとして使用されており、n
(おそらくサイズや要素数を示す符号なしの値)と比較されます。int32
のi
が負の値になった場合(これは通常、ループの条件が誤っているか、非常に大きなn
に対してint32
がオーバーフローした場合に起こりえます)、i < n
の比較がC言語のルールに従って予期せぬ結果を招く可能性があります。uint32
にすることで、i
が常に非負であることが保証され、n
との比較がより安全かつ直感的になります。これは、コードの堅牢性を高め、デバッグを容易にするための重要な改善です。
3. void*
ポインタのMLink*
への修正
src/runtime/malloc.h
のMSpan
構造体内のfreelist
メンバーと、src/runtime/mcentral.c
のMCentral_FreeList
関数のstart
引数の型がvoid*
からMLink*
に変更されたことは、型安全性の向上を目的としています。
MSpan
構造体:MSpan
はGoのメモリ管理において、連続したページ(メモリブロック)の範囲を表す構造体です。freelist
は、このページ範囲内に存在する解放されたオブジェクトを連結するリンクリストの先頭を指します。MLink
型:MLink
は、Goランタイムのメモリ管理で使用される、リンクリストのノードを表す構造体(または型エイリアス)です。通常、リンクリストの各要素の先頭に配置され、次の要素へのポインタを保持します。- 型安全性の向上:
void*
は任意の型のポインタを保持できますが、その型情報が失われます。そのため、freelist
が実際にMLink
型のオブジェクトを指しているにもかかわらずvoid*
として宣言されていると、コンパイラはfreelist
を介した操作(例:freelist->next
のようなアクセス)に対して厳密な型チェックを行えません。MLink*
に型を明示することで、コンパイラはfreelist
がMLink
型のオブジェクトを指すことを認識し、誤ったアクセスや操作に対してコンパイル時に警告やエラーを発するようになります。これにより、潜在的な実行時エラーを未然に防ぎ、コードの信頼性を高めることができます。
これらの変更は、Goランタイムの低レベルなメモリ管理コードの正確性と保守性を向上させる上で不可欠なものです。
関連リンク
- Go言語の初期開発に関する情報:
- The Go Programming Language (Official Website): https://go.dev/
- Go Blog (初期の投稿): https://go.dev/blog/
参考にした情報源リンク
- GCC documentation on identifiers (general information): https://gcc.gnu.org/onlinedocs/
- C Standard (C99 and later) for Unicode in identifiers: ISO/IEC 9899:1999 (C99)
- Signed vs Unsigned Integer Comparison in C:
- Stack Overflow discussions on C signed/unsigned comparison.
void*
in C and type safety:- General C programming best practices and discussions on
void*
.
- General C programming best practices and discussions on