[インデックス 1926] ファイルの概要
このコミットは、Go言語の初期のコンパイラである 8g
(386アーキテクチャ向けGoコンパイラ) の非常に初期段階の実装を導入するものです。以下のファイルが新規に追加されています。
src/cmd/8g/Makefile
:8g
コンパイラのビルド設定を定義するMakefileです。コンパイラのコンパイルに必要なソースファイル、ヘッダファイル、ライブラリ、およびリンク手順が記述されています。src/cmd/8g/align.c
: データのアライメント(メモリ配置の調整)に関する処理を扱うファイルです。特に386アーキテクチャにおける型のアライメントやサイズに関する初期設定が含まれています。src/cmd/8g/gen.c
: コード生成の主要なロジックを含むファイルです。Goの抽象構文木 (AST) をアセンブリ命令に変換する処理の骨格がここに記述されています。main
関数のような基本的な関数のコンパイル処理が含まれています。src/cmd/8g/gg.h
:8g
コンパイラ全体で共有される共通の定義やデータ構造(例:Addr
、Prog
構造体)が宣言されているヘッダファイルです。src/cmd/8g/list.c
: 生成されたアセンブリ命令やアドレス情報を整形して表示するためのユーティリティ関数が含まれています。デバッグやコンパイラの出力確認に利用されます。src/cmd/8g/obj.c
: オブジェクトファイル生成に関する処理を扱うファイルです。コンパイルされたコードを最終的なオブジェクトファイル形式で出力するためのロジックが含まれています。シンボル情報の管理やデータセクションの生成なども行われます。
コミット
Author: Russ Cox rsc@golang.org Date: Tue Mar 31 00:22:59 2009 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dc5b4678e2981bda6b6b8a7aa8737f202196374c
元コミット内容
minimal 8g. can compile
package main
func main() {
}
and not much else.
R=ken
OCL=26943
CL=26943
変更の背景
このコミットは、Go言語のコンパイラ開発における非常に初期の段階を示しています。Go言語は2009年にGoogleでオープンソースとして公開されましたが、その開発はそれ以前から進められていました。このコミットは、Go言語のプログラムを実際にコンパイルできる最小限のツールチェーンを構築する取り組みの一環として行われました。
具体的には、8g
(386アーキテクチャ向けGoコンパイラ) の最初のバージョンを導入し、package main; func main() {}
という最も単純なGoプログラムをコンパイルできる状態にすることを目指しています。これは、コンパイラの基本的な骨格を確立し、その後の機能拡張のための基盤を築くための重要なステップでした。この段階では、複雑な言語機能のサポートよりも、まずコンパイラが動作すること、そしてビルドシステムに統合されることが優先されました。
前提知識の解説
Goコンパイラ (8g)
Go言語の初期のコンパイラは、ターゲットアーキテクチャごとに異なる名前が付けられていました。8g
はIntel 386 (x86 32-bit) アーキテクチャ向けのGoコンパイラを指します。同様に、6g
はAMD64 (x86-64) 向け、5g
はARM向けなどがありました。これらのコンパイラは、Goのソースコードを対応するアーキテクチャのアセンブリコードに変換し、最終的に実行可能なバイナリを生成する役割を担っていました。
Plan 9 Cコンパイラツールチェーン
Go言語のコンパイラツールチェーンは、Bell LabsのPlan 9オペレーティングシステムで開発されたCコンパイラツールチェーン(8c
, 6c
, 5c
など)の影響を強く受けています。この影響は、ファイル名(例: align.c
, gen.c
, obj.c
など)や、コードの構造、さらには一部のコメント(例: Derived from Inferno utils/8c/
)からも見て取れます。Plan 9のツールチェーンは、シンプルさ、移植性、そしてクロスコンパイルの容易さを特徴としており、Goの初期開発においてこれらの特性が重要視されました。
アセンブラ (8l)
Goのコンパイラは、Goのソースコードを直接機械語に変換するのではなく、まずアセンブリコード(または中間表現)を生成します。このアセンブリコードを最終的な機械語に変換し、実行可能なバイナリを生成するのがアセンブラとリンカの役割です。8l
は386アーキテクチャ向けのアセンブラ/リンカを指し、8g
が生成したアセンブリコードを受け取って処理します。このコミットのMakefileにも $(LD)
(リンカ) の呼び出しが含まれており、8l
のようなツールが連携して動作することが示唆されています。
Goの初期開発
Go言語は、GoogleのRobert Griesemer、Rob Pike、Ken Thompsonによって設計されました。彼らは、既存のプログラミング言語の複雑さやコンパイル時間の長さに不満を抱き、よりシンプルで効率的な言語を目指しました。初期のGoコンパイラは、自己ホスト型(Go自身でGoをコンパイルできる)になる前に、C言語で書かれていました。このコミットは、そのC言語で書かれた初期コンパイラの基盤を構築する段階に当たります。
技術的詳細
このコミットは、8g
コンパイラの基本的な構造と機能を確立しています。
コンパイラの構成要素
- Makefile (
src/cmd/8g/Makefile
): コンパイラのビルドプロセスを管理します。8g
実行ファイルを生成するために、align.c
,gen.c
,obj.c
,list.c
などのCソースファイルをコンパイルし、8l
(リンカ) を使ってリンクする手順が定義されています。また、../gc/go.h
や../8l/8.out.h
といった共通ヘッダファイルへの依存関係も示されており、Goコンパイラが他のコンポーネント(ガベージコレクタやリンカ)と密接に連携していることがわかります。 - アライメント処理 (
src/cmd/8g/align.c
):betypeinit
関数で、int
,uint
,uintptr
,float
などのGoの組み込み型に対する386アーキテクチャ固有のサイズとアライメントが初期化されます。maxround
(最大アライメント境界) やwidthptr
(ポインタの幅) といった値が設定され、メモリ上でのデータ配置が適切に行われるようにします。 - コード生成 (
src/cmd/8g/gen.c
):compile
関数がGoの関数ノードを受け取り、アセンブリ命令を生成する中心的な役割を担います。この初期バージョンでは、package main; func main() {}
のような最小限の関数定義に対応するため、関数のエントリポイント (ATEXT
) やリターン命令 (ARET
) の生成、引数とスタックサイズの計算など、基本的な処理のみが実装されています。ginscall
関数は、Goルーチン (go
) や遅延実行 (defer
) のための関数呼び出しを生成するプレースホルダとして存在していますが、この時点ではfatal
エラーを返すのみで、本格的な実装はこれからです。 - 共通定義 (
src/cmd/8g/gg.h
):Addr
構造体はアセンブリ命令のオペランド(ソースやデスティネーション)を表し、オフセット、シンボル、タイプなどの情報を含みます。Prog
構造体は単一のアセンブリ命令を表し、オペコード (as
)、ソース (from
)、デスティネーション (to
)、行番号などの情報を含みます。これらの構造体は、コンパイラが中間表現としてアセンブリ命令を構築するために不可欠です。 - リスト出力 (
src/cmd/8g/list.c
):Pconv
,Dconv
,Rconv
,Aconv
,Yconv
といった関数は、Prog
(命令)、Addr
(アドレス)、レジスタ、アセンブリ命令のニーモニック、文字列定数などを整形して文字列に変換するためのフォーマット関数です。これらは主にデバッグ目的で、コンパイラが生成したアセンブリコードを人間が読みやすい形式で出力するために使用されます。 - オブジェクトファイル生成 (
src/cmd/8g/obj.c
):dumpfuncs
関数は、コンパイルされた関数ごとのアセンブリ命令リストをオブジェクトファイル形式で出力する主要な関数です。シンボル情報の管理 (zname
) や、ソースコードの行番号情報 (zhist
) の埋め込みも行われます。datastring
,dumpstrings
,dstringptr
などの関数は、文字列定数やその他のデータセクションをオブジェクトファイルに書き込むためのものです。
ミニマルなコンパイル機能
コミットメッセージにある通り、この 8g
は package main; func main() {}
という非常に単純なプログラムしかコンパイルできません。これは、コンパイラのフロントエンド(構文解析、意味解析)とバックエンド(コード生成、最適化)の間のインターフェースを確立し、最小限の実行可能なパスを確保することを目的としています。複雑な型システム、インターフェース、並行処理、ガベージコレクションなどの機能は、この時点ではまだ実装されていません。
アセンブリ生成の基礎
gen.c
の compile
関数は、Goの関数をアセンブリ命令のシーケンスに変換します。gins
関数は、新しいアセンブリ命令 (Prog
構造体) を生成し、命令リストに追加します。obj.c
の dumpfuncs
は、この命令リストを読み取り、オブジェクトファイルに書き出す役割を担います。このプロセスは、Goのソースコードから機械語への変換パイプラインの基礎を形成します。
データ構造
gg.h
で定義されている Addr
と Prog
構造体は、Goコンパイラがアセンブリ命令を表現するための中心的なデータ構造です。
Addr
は、命令のオペランド(ソースやデスティネーション)がメモリ上のどこにあるか、レジスタか、定数か、シンボルかといった情報を詳細に記述します。Prog
は、個々のアセンブリ命令(例:MOVL
,ADDL
,CALL
,RET
など)のオペコード、ソースオペランド、デスティネーションオペランド、そして命令が生成されたソースコードの行番号などを保持します。
これらの構造体を通じて、コンパイラはGoの高級な概念を低レベルのアセンブリ命令にマッピングします。
コアとなるコードの変更箇所
このコミットは全て新規ファイルの追加であるため、変更箇所というよりは、各ファイルの主要な役割を担う部分を挙げます。
-
src/cmd/8g/Makefile
:TARG=\ 8g HFILES=\ ../gc/go.h\\\ ../8l/8.out.h\\\ gg.h\\\ # opt.h\\\ OFILES=\ ../8l/enam.$O\\\ list.$O\\\ align.$O\\\ obj.$O\\\ gen.$O\\\ gsubr.$O\\\ cgen.$O\\\ # peep.$O\\\ # reg.$O\\\ LIB=\ ../gc/gc.a$O $(TARG): $(OFILES) $(LIB) $(LD) -o $(TARG) -L$(GOROOT)/lib $(OFILES) $(LIB) -lbio -l9 -lm
これは
8g
コンパイラのビルドターゲット、依存するヘッダファイル (HFILES
)、オブジェクトファイル (OFILES
)、およびリンク方法を定義しています。特に$(LD) -o $(TARG) ...
の行は、コンパイルされたオブジェクトファイルをリンカ (8l
に相当) を使って最終的な実行ファイル8g
に結合するコマンドです。 -
src/cmd/8g/align.c
:void betypeinit(void) { maxround = 4; widthptr = 4; zprog.link = P; zprog.as = AGOK; zprog.from.type = D_NONE; zprog.from.index = D_NONE; zprog.from.scale = 0; zprog.to = zprog.from; listinit(); }
betypeinit
関数は、386アーキテクチャにおける基本的な型情報(最大アライメント、ポインタサイズ)を設定し、zprog
というゼロ初期化された命令構造体を準備します。 -
src/cmd/8g/gen.c
:void compile(Node *fn) { // ... (省略) ... // set up domain for labels labellist = L; lno = setlineno(fn); curfn = fn; dowidth(curfn->type); // ... (省略) ... walk(curfn); if(nerrors != 0) goto ret; allocparams(); continpc = P; breakpc = P; pl = newplist(); pl->name = curfn->nname; pl->locals = autodcl; nodconst(&nod1, types[TINT32], 0); ptxt = gins(ATEXT, curfn->nname, &nod1); afunclit(&ptxt->from); // ginit(); gen(curfn->enter); gen(curfn->nbody); // gclean(); checklabels(); // if(curfn->type->outtuple != 0) // ginscall(throwreturn, 0); // if(hasdefer) // ginscall(deferreturn, 0); pc->as = ARET; // overwrite AEND pc->lineno = lineno; // if(!debug['N'] || debug['R'] || debug['P']) // regopt(ptxt); // fill in argument size ptxt->to.offset2 = rnd(curfn->type->argwid, maxround); // fill in final stack size ptxt->to.offset = rnd(stksize+maxarg, maxround); if(debug['f']) frame(0); ret: lineno = lno; }
compile
関数は、Goの関数ノードを受け取り、その関数に対応するアセンブリコードを生成します。gins(ATEXT, ...)
で関数のエントリポイントを定義し、gen(curfn->nbody)
で関数本体のコードを生成します。最後にpc->as = ARET;
で関数のリターン命令を設定します。 -
src/cmd/8g/obj.c
:void dumpfuncs(void) { // ... (省略) ... // fix up pc pcloc = 0; for(pl=plist; pl!=nil; pl=pl->link) { for(p=pl->firstpc; p!=P; p=p->link) { p->loc = pcloc; pcloc++; } } // put out functions for(pl=plist; pl!=nil; pl=pl->link) { // ... (省略) ... for(p=pl->firstpc; p!=P; p=p->link) { // ... (省略) ... Bputc(bout, p->as); Bputc(bout, p->as>>8); Bputc(bout, p->lineno); Bputc(bout, p->lineno>>8); Bputc(bout, p->lineno>>16); Bputc(bout, p->lineno>>24); zaddr(bout, &p->from, sf); zaddr(bout, &p->to, st); } } }
dumpfuncs
関数は、コンパイラが生成した命令リスト (plist
) を走査し、各命令 (Prog
) の情報をバイナリ形式で出力ストリーム (bout
) に書き込みます。これにより、オブジェクトファイルが生成されます。命令のオペコード、行番号、ソース/デスティネーションアドレスなどが書き込まれます。
コアとなるコードの解説
-
Makefile
の解説: このMakefileは、Goコンパイラがどのようにビルドされるかを示しています。8g
という実行ファイルを生成するために、複数のCソースファイルがコンパイルされ、../gc/gc.a$O
(ガベージコレクタ関連のライブラリ) や../8l/8.out.h
(リンカ関連のヘッダ) といった他のGoツールチェーンのコンポーネントと連携してリンクされることがわかります。これは、Goのコンパイラが単一の巨大なプログラムではなく、複数の小さなツールが連携して動作する「ツールチェーン」として設計されていることを示唆しています。 -
align.c
のbetypeinit
関数の解説: この関数は、Goの型がメモリ上でどのように表現されるかの基本的なルールを設定します。maxround = 4
は、メモリのアライメントが4バイト境界で行われることを意味し、widthptr = 4
はポインタのサイズが4バイトであることを示します。これは32ビットアーキテクチャである386の特性を反映しています。zprog
の初期化は、新しい命令を生成する際のテンプレートとして使用される、ゼロ値で埋められた命令構造体を準備するものです。 -
gen.c
のcompile
関数の解説:compile
関数は、Goの関数をアセンブリコードに変換する「バックエンド」の入り口です。setlineno(fn)
: ソースコードの行番号を設定し、デバッグ情報に利用します。curfn = fn; dowidth(curfn->type);
: 現在コンパイル中の関数を設定し、その関数の型情報(引数や戻り値のサイズなど)を計算します。walk(curfn);
: これはコンパイラの「フロントエンド」の一部であり、Goの抽象構文木 (AST) を走査し、意味解析や最適化の初期パスを実行します。このコミットではまだ最小限の機能しか持たないと推測されます。allocparams();
: 関数の引数やローカル変数のためのスタックフレームを割り当てます。gins(ATEXT, curfn->nname, &nod1);
:ATEXT
命令は、新しい関数の開始を示します。curfn->nname
は関数の名前を表し、nod1
は関数のフラグや属性(この場合は0)を設定します。gen(curfn->enter); gen(curfn->nbody);
:curfn->enter
は関数のプロローグ(スタックフレームのセットアップなど)を、curfn->nbody
は関数本体のGoコードに対応するアセンブリ命令を生成します。pc->as = ARET;
:ARET
命令は関数のリターンを示します。ptxt->to.offset2 = rnd(curfn->type->argwid, maxround);
: 関数の引数の合計サイズを計算し、オブジェクトファイルに書き込むための情報を設定します。ptxt->to.offset = rnd(stksize+maxarg, maxround);
: 関数のスタックフレームの合計サイズ(ローカル変数と引数の最大サイズを含む)を計算し、設定します。
-
obj.c
のdumpfuncs
関数の解説:dumpfuncs
は、コンパイラがメモリ上に構築したアセンブリ命令のリストを、最終的なオブジェクトファイル形式でディスクに書き出す役割を担います。pcloc
の修正: 各命令にユニークなプログラムカウンタ (PC) オフセットを割り当てます。これは、命令のアドレス解決やジャンプ命令のターゲット計算に必要です。- シンボル情報の出力:
zname
関数を使って、関数名や変数名などのシンボル情報をオブジェクトファイルに書き込みます。これにより、リンカが異なるオブジェクトファイル間でシンボルを解決できるようになります。 - 命令のバイナリ出力: 各
Prog
構造体の内容(オペコード、行番号、オペランドのアドレス情報など)をバイナリ形式で出力ストリーム (bout
) に書き込みます。zaddr
関数は、Addr
構造体の詳細な情報をバイナリ形式に変換します。
これらのコードは、Goのソースコードを読み込み、それをアセンブリ命令に変換し、最終的に実行可能なバイナリの一部となるオブジェクトファイルを生成するという、コンパイラの基本的なパイプラインを形成しています。
関連リンク
- Go言語公式サイト: https://go.dev/
- Go言語の歴史に関するドキュメント: https://go.dev/doc/history
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Plan 9 from Bell Labs: https://9p.io/plan9/
- Inferno Operating System: https://www.vitanuova.com/inferno/
- Go compiler internals (general concepts): Web search for "Go compiler internals", "Go 8g compiler", "Plan 9 compiler influence on Go".
- 特に、Goの初期のコンパイラがPlan 9のツールチェーンから派生したという情報は、Goの公式ドキュメントや関連する技術ブログで広く言及されています。
8g
のような命名規則は、Plan 9のツールチェーンの慣習に由来します。- GoのコンパイラがどのようにASTを処理し、アセンブリを生成するかについての一般的な知識は、コンパイラ設計の原則に基づいています。```markdown
[インデックス 1926] ファイルの概要
このコミットは、Go言語の初期のコンパイラである 8g
(386アーキテクチャ向けGoコンパイラ) の非常に初期段階の実装を導入するものです。以下のファイルが新規に追加されています。
src/cmd/8g/Makefile
:8g
コンパイラのビルド設定を定義するMakefileです。コンパイラのコンパイルに必要なソースファイル、ヘッダファイル、ライブラリ、およびリンク手順が記述されています。src/cmd/8g/align.c
: データのアライメント(メモリ配置の調整)に関する処理を扱うファイルです。特に386アーキテクチャにおける型のアライメントやサイズに関する初期設定が含まれています。src/cmd/8g/gen.c
: コード生成の主要なロジックを含むファイルです。Goの抽象構文木 (AST) をアセンブリ命令に変換する処理の骨格がここに記述されています。main
関数のような基本的な関数のコンパイル処理が含まれています。src/cmd/8g/gg.h
:8g
コンパイラ全体で共有される共通の定義やデータ構造(例:Addr
、Prog
構造体)が宣言されているヘッダファイルです。src/cmd/8g/list.c
: 生成されたアセンブリ命令やアドレス情報を整形して表示するためのユーティリティ関数が含まれています。デバッグやコンパイラの出力確認に利用されます。src/cmd/8g/obj.c
: オブジェクトファイル生成に関する処理を扱うファイルです。コンパイルされたコードを最終的なオブジェクトファイル形式で出力するためのロジックが含まれています。シンボル情報の管理やデータセクションの生成なども行われます。
コミット
Author: Russ Cox rsc@golang.org Date: Tue Mar 31 00:22:59 2009 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dc5b4678e2981bda6b6b8a7aa8737f202196374c
元コミット内容
minimal 8g. can compile
package main
func main() {
}
and not much else.
R=ken
OCL=26943
CL=26943
変更の背景
このコミットは、Go言語のコンパイラ開発における非常に初期の段階を示しています。Go言語は2009年にGoogleでオープンソースとして公開されましたが、その開発はそれ以前から進められていました。このコミットは、Go言語のプログラムを実際にコンパイルできる最小限のツールチェーンを構築する取り組みの一環として行われました。
具体的には、8g
(386アーキテクチャ向けGoコンパイラ) の最初のバージョンを導入し、package main; func main() {}
という最も単純なGoプログラムをコンパイルできる状態にすることを目指しています。これは、コンパイラの基本的な骨格を確立し、その後の機能拡張のための基盤を築くための重要なステップでした。この段階では、複雑な言語機能のサポートよりも、まずコンパイラが動作すること、そしてビルドシステムに統合されることが優先されました。
前提知識の解説
Goコンパイラ (8g)
Go言語の初期のコンパイラは、ターゲットアーキテクチャごとに異なる名前が付けられていました。8g
はIntel 386 (x86 32-bit) アーキテクチャ向けのGoコンパイラを指します。同様に、6g
はAMD64 (x86-64) 向け、5g
はARM向けなどがありました。これらのコンパイラは、Goのソースコードを対応するアーキテクチャのアセンブリコードに変換し、最終的に実行可能なバイナリを生成する役割を担っていました。
Plan 9 Cコンパイラツールチェーン
Go言語のコンパイラツールチェーンは、Bell LabsのPlan 9オペレーティングシステムで開発されたCコンパイラツールチェーン(8c
, 6c
, 5c
など)の影響を強く受けています。この影響は、ファイル名(例: align.c
, gen.c
, obj.c
など)や、コードの構造、さらには一部のコメント(例: Derived from Inferno utils/8c/
)からも見て取れます。Plan 9のツールチェーンは、シンプルさ、移植性、そしてクロスコンパイルの容易さを特徴としており、Goの初期開発においてこれらの特性が重要視されました。
アセンブラ (8l)
Goのコンパイラは、Goのソースコードを直接機械語に変換するのではなく、まずアセンブリコード(または中間表現)を生成します。このアセンブリコードを最終的な機械語に変換し、実行可能なバイナリを生成するのがアセンブラとリンカの役割です。8l
は386アーキテクチャ向けのアセンブラ/リンカを指し、8g
が生成したアセンブリコードを受け取って処理します。このコミットのMakefileにも $(LD)
(リンカ) の呼び出しが含まれており、8l
のようなツールが連携して動作することが示唆されています。
Goの初期開発
Go言語は、GoogleのRobert Griesemer、Rob Pike、Ken Thompsonによって設計されました。彼らは、既存のプログラミング言語の複雑さやコンパイル時間の長さに不満を抱き、よりシンプルで効率的な言語を目指しました。初期のGoコンパイラは、自己ホスト型(Go自身でGoをコンパイルできる)になる前に、C言語で書かれていました。このコミットは、そのC言語で書かれた初期コンパイラの基盤を構築する段階に当たります。
技術的詳細
このコミットは、8g
コンパイラの基本的な構造と機能を確立しています。
コンパイラの構成要素
- Makefile (
src/cmd/8g/Makefile
): コンパイラのビルドプロセスを管理します。8g
実行ファイルを生成するために、align.c
,gen.c
,obj.c
,list.c
などのCソースファイルをコンパイルし、8l
(リンカ) を使ってリンクする手順が定義されています。また、../gc/go.h
や../8l/8.out.h
といった共通ヘッダファイルへの依存関係も示されており、Goコンパイラが他のコンポーネント(ガベージコレクタやリンカ)と密接に連携していることがわかります。 - アライメント処理 (
src/cmd/8g/align.c
):betypeinit
関数で、int
,uint
,uintptr
,float
などのGoの組み込み型に対する386アーキテクチャ固有のサイズとアライメントが初期化されます。maxround
(最大アライメント境界) やwidthptr
(ポインタの幅) といった値が設定され、メモリ上でのデータ配置が適切に行われるようにします。 - コード生成 (
src/cmd/8g/gen.c
):compile
関数がGoの関数ノードを受け取り、アセンブリ命令を生成する中心的な役割を担います。この初期バージョンでは、package main; func main() {}
のような最小限の関数定義に対応するため、関数のエントリポイント (ATEXT
) やリターン命令 (ARET
) の生成、引数とスタックサイズの計算など、基本的な処理のみが実装されています。ginscall
関数は、Goルーチン (go
) や遅延実行 (defer
) のための関数呼び出しを生成するプレースホルダとして存在していますが、この時点ではfatal
エラーを返すのみで、本格的な実装はこれからです。 - 共通定義 (
src/cmd/8g/gg.h
):Addr
構造体はアセンブリ命令のオペランド(ソースやデスティネーション)を表し、オフセット、シンボル、タイプなどの情報を含みます。Prog
構造体は単一のアセンブリ命令を表し、オペコード (as
)、ソース (from
)、デスティネーション (to
)、行番号などの情報を含みます。これらの構造体は、コンパイラが中間表現としてアセンブリ命令を構築するために不可欠です。 - リスト出力 (
src/cmd/8g/list.c
):Pconv
,Dconv
,Rconv
,Aconv
,Yconv
といった関数は、Prog
(命令)、Addr
(アドレス)、レジスタ、アセンブリ命令のニーモニック、文字列定数などを整形して文字列に変換するためのフォーマット関数です。これらは主にデバッグ目的で、コンパイラが生成したアセンブリコードを人間が読みやすい形式で出力するために使用されます。 - オブジェクトファイル生成 (
src/cmd/8g/obj.c
):dumpfuncs
関数は、コンパイルされた関数ごとのアセンブリ命令リストをオブジェクトファイル形式で出力する主要な関数です。シンボル情報の管理 (zname
) や、ソースコードの行番号情報 (zhist
) の埋め込みも行われます。datastring
,dumpstrings
,dstringptr
などの関数は、文字列定数やその他のデータセクションをオブジェクトファイルに書き込むためのものです。
ミニマルなコンパイル機能
コミットメッセージにある通り、この 8g
は package main; func main() {}
という非常に単純なプログラムしかコンパイルできません。これは、コンパイラのフロントエンド(構文解析、意味解析)とバックエンド(コード生成、最適化)の間のインターフェースを確立し、最小限の実行可能なパスを確保することを目的としています。複雑な型システム、インターフェース、並行処理、ガベージコレクションなどの機能は、この時点ではまだ実装されていません。
アセンブリ生成の基礎
gen.c
の compile
関数は、Goの関数をアセンブリ命令のシーケンスに変換します。gins
関数は、新しいアセンブリ命令 (Prog
構造体) を生成し、命令リストに追加します。obj.c
の dumpfuncs
は、この命令リストを読み取り、オブジェクトファイルに書き出す役割を担います。このプロセスは、Goのソースコードから機械語への変換パイプラインの基礎を形成します。
データ構造
gg.h
で定義されている Addr
と Prog
構造体は、Goコンパイラがアセンブリ命令を表現するための中心的なデータ構造です。
Addr
は、命令のオペランド(ソースやデスティネーション)がメモリ上のどこにあるか、レジスタか、定数か、シンボルかといった情報を詳細に記述します。Prog
は、個々のアセンブリ命令(例:MOVL
,ADDL
,CALL
,RET
など)のオペコード、ソースオペランド、デスティネーションオペランド、そして命令が生成されたソースコードの行番号などを保持します。
これらの構造体を通じて、コンパイラはGoの高級な概念を低レベルのアセンブリ命令にマッピングします。
コアとなるコードの変更箇所
このコミットは全て新規ファイルの追加であるため、変更箇所というよりは、各ファイルの主要な役割を担う部分を挙げます。
-
src/cmd/8g/Makefile
:TARG=\ 8g HFILES=\ ../gc/go.h\\\ ../8l/8.out.h\\\ gg.h\\\ # opt.h\\\ OFILES=\ ../8l/enam.$O\\\ list.$O\\\ align.$O\\\ obj.$O\\\ gen.$O\\\ gsubr.$O\\\ cgen.$O\\\ # peep.$O\\\ # reg.$O\\\ LIB=\ ../gc/gc.a$O $(TARG): $(OFILES) $(LIB) $(LD) -o $(TARG) -L$(GOROOT)/lib $(OFILES) $(LIB) -lbio -l9 -lm
これは
8g
コンパイラのビルドターゲット、依存するヘッダファイル (HFILES
)、オブジェクトファイル (OFILES
)、およびリンク方法を定義しています。特に$(LD) -o $(TARG) ...
の行は、コンパイルされたオブジェクトファイルをリンカ (8l
に相当) を使って最終的な実行ファイル8g
に結合するコマンドです。 -
src/cmd/8g/align.c
:void betypeinit(void) { maxround = 4; widthptr = 4; zprog.link = P; zprog.as = AGOK; zprog.from.type = D_NONE; zprog.from.index = D_NONE; zprog.from.scale = 0; zprog.to = zprog.from; listinit(); }
betypeinit
関数は、386アーキテクチャにおける基本的な型情報(最大アライメント、ポインタサイズ)を設定し、zprog
というゼロ初期化された命令構造体を準備します。 -
src/cmd/8g/gen.c
:void compile(Node *fn) { // ... (省略) ... // set up domain for labels labellist = L; lno = setlineno(fn); curfn = fn; dowidth(curfn->type); // ... (省略) ... walk(curfn); if(nerrors != 0) goto ret; allocparams(); continpc = P; breakpc = P; pl = newplist(); pl->name = curfn->nname; pl->locals = autodcl; nodconst(&nod1, types[TINT32], 0); ptxt = gins(ATEXT, curfn->nname, &nod1); afunclit(&ptxt->from); // ginit(); gen(curfn->enter); gen(curfn->nbody); // gclean(); checklabels(); // if(curfn->type->outtuple != 0) // ginscall(throwreturn, 0); // if(hasdefer) // ginscall(deferreturn, 0); pc->as = ARET; // overwrite AEND pc->lineno = lineno; // if(!debug['N'] || debug['R'] || debug['P']) // regopt(ptxt); // fill in argument size ptxt->to.offset2 = rnd(curfn->type->argwid, maxround); // fill in final stack size ptxt->to.offset = rnd(stksize+maxarg, maxround); if(debug['f']) frame(0); ret: lineno = lno; }
compile
関数は、Goの関数ノードを受け取り、その関数に対応するアセンブリコードを生成します。gins(ATEXT, ...)
で関数のエントリポイントを定義し、gen(curfn->nbody)
で関数本体のコードを生成します。最後にpc->as = ARET;
で関数のリターン命令を設定します。 -
src/cmd/8g/obj.c
:void dumpfuncs(void) { // ... (省略) ... // fix up pc pcloc = 0; for(pl=plist; pl!=nil; pl=pl->link) { for(p=pl->firstpc; p!=P; p=p->link) { p->loc = pcloc; pcloc++; } } // put out functions for(pl=plist; pl!=nil; pl=pl->link) { // ... (省略) ... Bputc(bout, p->as); Bputc(bout, p->as>>8); Bputc(bout, p->lineno); Bputc(bout, p->lineno>>8); Bputc(bout, p->lineno>>16); Bputc(bout, p->lineno>>24); zaddr(bout, &p->from, sf); zaddr(bout, &p->to, st); } } }
dumpfuncs
関数は、コンパイラが生成した命令リスト (plist
) を走査し、各命令 (Prog
) の情報をバイナリ形式で出力ストリーム (bout
) に書き込みます。これにより、オブジェクトファイルが生成されます。命令のオペコード、行番号、ソース/デスティネーションアドレスなどが書き込まれます。
コアとなるコードの解説
-
Makefile
の解説: このMakefileは、Goコンパイラがどのようにビルドされるかを示しています。8g
という実行ファイルを生成するために、複数のCソースファイルがコンパイルされ、../gc/gc.a$O
(ガベージコレクタ関連のライブラリ) や../8l/8.out.h
(リンカ関連のヘッダ) といった他のGoツールチェーンのコンポーネントと連携してリンクされることがわかります。これは、Goのコンパイラが単一の巨大なプログラムではなく、複数の小さなツールが連携して動作する「ツールチェーン」として設計されていることを示唆しています。 -
align.c
のbetypeinit
関数の解説: この関数は、Goの型がメモリ上でどのように表現されるかの基本的なルールを設定します。maxround = 4
は、メモリのアライメントが4バイト境界で行われることを意味し、widthptr = 4
はポインタのサイズが4バイトであることを示します。これは32ビットアーキテクチャである386の特性を反映しています。zprog
の初期化は、新しい命令を生成する際のテンプレートとして使用される、ゼロ値で埋められた命令構造体を準備するものです。 -
gen.c
のcompile
関数の解説:compile
関数は、Goの関数をアセンブリコードに変換する「バックエンド」の入り口です。setlineno(fn)
: ソースコードの行番号を設定し、デバッグ情報に利用します。curfn = fn; dowidth(curfn->type);
: 現在コンパイル中の関数を設定し、その関数の型情報(引数や戻り値のサイズなど)を計算します。walk(curfn);
: これはコンパイラの「フロントエンド」の一部であり、Goの抽象構文木 (AST) を走査し、意味解析や最適化の初期パスを実行します。このコミットではまだ最小限の機能しか持たないと推測されます。allocparams();
: 関数の引数やローカル変数のためのスタックフレームを割り当てます。gins(ATEXT, curfn->nname, &nod1);
:ATEXT
命令は、新しい関数の開始を示します。curfn->nname
は関数の名前を表し、nod1
は関数のフラグや属性(この場合は0)を設定します。gen(curfn->enter); gen(curfn->nbody);
:curfn->enter
は関数のプロローグ(スタックフレームのセットアップなど)を、curfn->nbody
は関数本体のGoコードに対応するアセンブリ命令を生成します。pc->as = ARET;
:ARET
命令は関数のリターンを示します。ptxt->to.offset2 = rnd(curfn->type->argwid, maxround);
: 関数の引数の合計サイズを計算し、オブジェクトファイルに書き込むための情報を設定します。ptxt->to.offset = rnd(stksize+maxarg, maxround);
: 関数のスタックフレームの合計サイズ(ローカル変数と引数の最大サイズを含む)を計算し、設定します。
-
obj.c
のdumpfuncs
関数の解説:dumpfuncs
は、コンパイラがメモリ上に構築したアセンブリ命令のリストを、最終的なオブジェクトファイル形式でディスクに書き出す役割を担います。pcloc
の修正: 各命令にユニークなプログラムカウンタ (PC) オフセットを割り当てます。これは、命令のアドレス解決やジャンプ命令のターゲット計算に必要です。- シンボル情報の出力:
zname
関数を使って、関数名や変数名などのシンボル情報をオブジェクトファイルに書き込みます。これにより、リンカが異なるオブジェクトファイル間でシンボルを解決できるようになります。 - 命令のバイナリ出力: 各
Prog
構造体の内容(オペコード、行番号、オペランドのアドレス情報など)をバイナリ形式で出力ストリーム (bout
) に書き込みます。zaddr
関数は、Addr
構造体の詳細な情報をバイナリ形式に変換します。
これらのコードは、Goのソースコードを読み込み、それをアセンブリ命令に変換し、最終的に実行可能なバイナリの一部となるオブジェクトファイルを生成するという、コンパイラの基本的なパイプラインを形成しています。
関連リンク
- Go言語公式サイト: https://go.dev/
- Go言語の歴史に関するドキュメント: https://go.dev/doc/history
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Plan 9 from Bell Labs: https://9p.io/plan9/
- Inferno Operating System: https://www.vitanuova.com/inferno/
- Go compiler internals (general concepts): Web search for "Go compiler internals", "Go 8g compiler", "Plan 9 compiler influence on Go".
- 特に、Goの初期のコンパイラがPlan 9のツールチェーンから派生したという情報は、Goの公式ドキュメントや関連する技術ブログで広く言及されています。
8g
のような命名規則は、Plan 9のツールチェーンの慣習に由来します。- GoのコンパイラがどのようにASTを処理し、アセンブリを生成するかについての一般的な知識は、コンパイラ設計の原則に基づいています。