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

[インデックス 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 コンパイラ全体で共有される共通の定義やデータ構造(例: AddrProg 構造体)が宣言されているヘッダファイルです。
  • 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 などの関数は、文字列定数やその他のデータセクションをオブジェクトファイルに書き込むためのものです。

ミニマルなコンパイル機能

コミットメッセージにある通り、この 8gpackage main; func main() {} という非常に単純なプログラムしかコンパイルできません。これは、コンパイラのフロントエンド(構文解析、意味解析)とバックエンド(コード生成、最適化)の間のインターフェースを確立し、最小限の実行可能なパスを確保することを目的としています。複雑な型システム、インターフェース、並行処理、ガベージコレクションなどの機能は、この時点ではまだ実装されていません。

アセンブリ生成の基礎

gen.ccompile 関数は、Goの関数をアセンブリ命令のシーケンスに変換します。gins 関数は、新しいアセンブリ命令 (Prog 構造体) を生成し、命令リストに追加します。obj.cdumpfuncs は、この命令リストを読み取り、オブジェクトファイルに書き出す役割を担います。このプロセスは、Goのソースコードから機械語への変換パイプラインの基礎を形成します。

データ構造

gg.h で定義されている AddrProg 構造体は、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.cbetypeinit 関数の解説: この関数は、Goの型がメモリ上でどのように表現されるかの基本的なルールを設定します。maxround = 4 は、メモリのアライメントが4バイト境界で行われることを意味し、widthptr = 4 はポインタのサイズが4バイトであることを示します。これは32ビットアーキテクチャである386の特性を反映しています。zprog の初期化は、新しい命令を生成する際のテンプレートとして使用される、ゼロ値で埋められた命令構造体を準備するものです。

  • gen.ccompile 関数の解説: 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.cdumpfuncs 関数の解説: dumpfuncs は、コンパイラがメモリ上に構築したアセンブリ命令のリストを、最終的なオブジェクトファイル形式でディスクに書き出す役割を担います。

    • pcloc の修正: 各命令にユニークなプログラムカウンタ (PC) オフセットを割り当てます。これは、命令のアドレス解決やジャンプ命令のターゲット計算に必要です。
    • シンボル情報の出力: zname 関数を使って、関数名や変数名などのシンボル情報をオブジェクトファイルに書き込みます。これにより、リンカが異なるオブジェクトファイル間でシンボルを解決できるようになります。
    • 命令のバイナリ出力: 各 Prog 構造体の内容(オペコード、行番号、オペランドのアドレス情報など)をバイナリ形式で出力ストリーム (bout) に書き込みます。zaddr 関数は、Addr 構造体の詳細な情報をバイナリ形式に変換します。

これらのコードは、Goのソースコードを読み込み、それをアセンブリ命令に変換し、最終的に実行可能なバイナリの一部となるオブジェクトファイルを生成するという、コンパイラの基本的なパイプラインを形成しています。

関連リンク

参考にした情報源リンク

  • 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 コンパイラ全体で共有される共通の定義やデータ構造(例: AddrProg 構造体)が宣言されているヘッダファイルです。
  • 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 などの関数は、文字列定数やその他のデータセクションをオブジェクトファイルに書き込むためのものです。

ミニマルなコンパイル機能

コミットメッセージにある通り、この 8gpackage main; func main() {} という非常に単純なプログラムしかコンパイルできません。これは、コンパイラのフロントエンド(構文解析、意味解析)とバックエンド(コード生成、最適化)の間のインターフェースを確立し、最小限の実行可能なパスを確保することを目的としています。複雑な型システム、インターフェース、並行処理、ガベージコレクションなどの機能は、この時点ではまだ実装されていません。

アセンブリ生成の基礎

gen.ccompile 関数は、Goの関数をアセンブリ命令のシーケンスに変換します。gins 関数は、新しいアセンブリ命令 (Prog 構造体) を生成し、命令リストに追加します。obj.cdumpfuncs は、この命令リストを読み取り、オブジェクトファイルに書き出す役割を担います。このプロセスは、Goのソースコードから機械語への変換パイプラインの基礎を形成します。

データ構造

gg.h で定義されている AddrProg 構造体は、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.cbetypeinit 関数の解説: この関数は、Goの型がメモリ上でどのように表現されるかの基本的なルールを設定します。maxround = 4 は、メモリのアライメントが4バイト境界で行われることを意味し、widthptr = 4 はポインタのサイズが4バイトであることを示します。これは32ビットアーキテクチャである386の特性を反映しています。zprog の初期化は、新しい命令を生成する際のテンプレートとして使用される、ゼロ値で埋められた命令構造体を準備するものです。

  • gen.ccompile 関数の解説: 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.cdumpfuncs 関数の解説: dumpfuncs は、コンパイラがメモリ上に構築したアセンブリ命令のリストを、最終的なオブジェクトファイル形式でディスクに書き出す役割を担います。

    • pcloc の修正: 各命令にユニークなプログラムカウンタ (PC) オフセットを割り当てます。これは、命令のアドレス解決やジャンプ命令のターゲット計算に必要です。
    • シンボル情報の出力: zname 関数を使って、関数名や変数名などのシンボル情報をオブジェクトファイルに書き込みます。これにより、リンカが異なるオブジェクトファイル間でシンボルを解決できるようになります。
    • 命令のバイナリ出力: 各 Prog 構造体の内容(オペコード、行番号、オペランドのアドレス情報など)をバイナリ形式で出力ストリーム (bout) に書き込みます。zaddr 関数は、Addr 構造体の詳細な情報をバイナリ形式に変換します。

これらのコードは、Goのソースコードを読み込み、それをアセンブリ命令に変換し、最終的に実行可能なバイナリの一部となるオブジェクトファイルを生成するという、コンパイラの基本的なパイプラインを形成しています。

関連リンク

参考にした情報源リンク

  • 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を処理し、アセンブリを生成するかについての一般的な知識は、コンパイラ設計の原則に基づいています。