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

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

このコミットは、Go言語の初期のコンパイラである6g(x86-64アーキテクチャ向け)のソースコードに対する変更です。主に以下のファイルが影響を受けています。

  • src/cmd/6g/cgen.c: コード生成の汎用ルーチンが含まれるファイル。
  • src/cmd/6g/gen.c: コード生成の主要なロジックが含まれるファイル。
  • src/cmd/6g/gg.h: 6gコンパイラで使用される共通の定義や関数プロトタイプが含まれるヘッダファイル。
  • src/cmd/6g/gsubr.c: コード生成のサブルーチンやユーティリティ関数が含まれるファイル。
  • src/cmd/gc/go.h: Goコンパイラ全体で共有される共通の定義が含まれるヘッダファイル。

これらのファイルは、Goのソースコードを機械語に変換するコンパイラのバックエンド部分を構成しています。

コミット

add comments and delete dead code

R=ken
OCL=22078
CL=22080

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

https://github.com/golang/go/commit/2d259c817a5edda82a98befd49212f3e47eac5e3

元コミット内容

commit 2d259c817a5edda82a98befd49212f3e47eac5e3
Author: Russ Cox <rsc@golang.org>
Date:   Mon Jan 5 17:32:23 2009 -0800

    add comments and delete dead code
    
    R=ken
    OCL=22078
    CL=22080

変更の背景

このコミットは、Go言語がまだ開発の初期段階にあった2009年1月に行われました。当時のGoコンパイラは、現在とは異なる設計思想や実装の詳細を持っていました。このような初期段階のプロジェクトでは、コードベースの急速な進化に伴い、以下のような課題が生じがちです。

  1. コードの可読性と保守性の低下: 新機能の追加や既存コードの変更が頻繁に行われるため、コードの意図や動作が不明瞭になることがあります。特に、コンパイラのような複雑なシステムでは、各関数の役割や引数の意味を明確にすることが不可欠です。
  2. デッドコードの蓄積: 実験的な機能、古い設計の名残、あるいは単に未使用になったコードが残存することがあります。これらのデッドコードは、コードベースを不必要に肥大化させ、理解を妨げ、将来的なバグの原因となる可能性があります。
  3. 命名の一貫性と明確性の欠如: 開発の初期段階では、概念や操作に対する最適な命名がまだ確立されていないことがあります。これにより、同じような操作が異なる名前で呼ばれたり、曖昧な名前が使われたりすることがあります。

このコミットは、これらの課題に対処し、6gコンパイラのコードベースの品質を向上させることを目的としています。具体的には、重要なコード生成ルーチンに詳細なコメントを追加することで可読性を高め、もはや使用されていないデッドコードを削除することでコードベースを整理し、一部の内部的なオペコードの命名をより明確なものに変更しています。

前提知識の解説

このコミットを理解するためには、Go言語の初期のコンパイラ設計と、一般的なコンパイラのコード生成フェーズに関する基本的な知識が必要です。

Goコンパイラ (6g)

Go言語の初期(2009年頃)には、各アーキテクチャ向けに独立したコンパイラが存在しました。6gは、x86-64(AMD64)アーキテクチャ向けのGoコンパイラを指します。これらのコンパイラは、Goのソースコードを解析し、抽象構文木(AST)を構築した後、中間表現を経て最終的なアセンブリコードを生成する役割を担っていました。

コンパイラのコード生成フェーズ

コンパイラの主要なフェーズの一つに「コード生成」があります。このフェーズでは、コンパイラはプログラムの抽象的な表現(ASTや中間表現)を受け取り、ターゲットとなるCPUアーキテクチャの具体的な機械語命令(またはアセンブリ命令)に変換します。このプロセスには、レジスタ割り当て、命令選択、命令スケジューリングなどが含まれます。

主要なデータ構造と関数

  • Node構造体: 抽象構文木(AST)のノードを表します。変数、定数、演算子、関数呼び出しなど、プログラムのあらゆる要素がNodeとして表現されます。
  • Type構造体: Go言語の型システムにおける型情報を表します。例えば、intstring、構造体、関数型などです。
  • Prog構造体: 生成されるアセンブリ命令の一つを表します。オペコード(命令の種類)、オペランド(命令の対象となるデータやレジスタ)、次の命令へのポインタなどが含まれます。
  • cgen, agen, igen, bgen, sgen: これらは6gコンパイラにおける主要なコード生成ルーチンの一部です。
    • cgen: 値のコピーや代入など、一般的なコード生成を行います。
    • agen: アドレス(ポインタ)の生成を行います。
    • igen: 間接参照(ポインタの指す値の取得)のコード生成を行います。
    • bgen: 条件分岐(if文など)のコード生成を行います。
    • sgen: 構造体や配列などのブロックコピーのコード生成を行います。
  • regalloc, regfree: レジスタ割り当て(regalloc)とレジスタ解放(regfree)を行う関数です。コンパイラは、計算中に値を一時的に保持するためにCPUのレジスタを使用しますが、レジスタの数は限られているため、効率的な割り当てと解放が必要です。
  • gins: アセンブリ命令(Prog構造体)を生成し、命令リストに追加する関数です。
  • optoas: Goコンパイラの内部的な抽象オペコード(OASなど、Oで始まる定数)を、ターゲットアーキテクチャの具体的なアセンブリ命令(Aで始まる定数、例: AMOVL)に変換する関数です。

OFORからOEXTENDへの変更

このコミットでは、Goコンパイラの内部的なオペコードの一つであるOFOROEXTENDに変更されています。

  • OFOR: 変更前のオペコード。その名前からだけでは具体的な意味が不明瞭です。おそらく、何らかの「拡張」操作や型変換に関連する汎用的なオペコードとして使われていた可能性があります。
  • OEXTEND: 変更後のオペコード。これは「拡張」を意味し、特に符号拡張(signed extension)やゼロ拡張(zero extension)といった、より小さいサイズの整数値を大きいサイズのレジスタにロードする際に、その値の符号ビットやゼロで上位ビットを埋める操作を指すことが多いです。例えば、8ビットの符号付き整数を32ビットレジスタにロードする際に、元の8ビットの最上位ビット(符号ビット)を32ビットの残りの上位ビットにコピーする操作などがこれに該当します。

この変更は、コンパイラ内部のオペコードのセマンティクスをより明確にし、コードの意図を分かりやすくするためのリファクタリングと考えられます。

技術的詳細

このコミットの技術的な変更は、主に以下の3つのカテゴリに分けられます。

  1. コメントの追加と改善:

    • src/cmd/6g/cgen.csrc/cmd/6g/gen.csrc/cmd/6g/gsubr.c内の多くのコード生成関数に、その関数の目的、引数、生成されるアセンブリコードのパターンを説明する詳細なコメントが追加されました。これにより、これらの関数の役割が明確になり、コードの可読性が大幅に向上しています。
    • 例えば、cgen関数には「res = n; を生成する」というコメントが、agen関数には「res = &n; を生成する」というコメントが追加されています。これは、コンパイラのコード生成ルーチンが、Go言語の抽象的な操作をどのように具体的なアセンブリ命令にマッピングするかを理解する上で非常に役立ちます。
  2. デッドコードの削除:

    • src/cmd/6g/gen.cから、未使用の列挙型メンバーAJMPXが削除されました。これは、おそらく過去の実験的な機能や、もはや必要とされないアセンブリ命令のプレースホルダーだったと考えられます。
    • src/cmd/6g/gg.hsrc/cmd/6g/gsubr.cから、regsallocという関数プロトタイプと関数定義が削除されました。この関数はfatal("regsalloc");という実装しか持っておらず、実際に使用されていなかったか、あるいは別の方法で置き換えられたデッドコードであったことが示唆されます。
    • src/cmd/6g/gsubr.cから、brunsignedという関数が削除されました。この関数は、条件分岐命令の符号なしバージョンを返すことを意図していましたが、おそらく使用されていなかったか、その機能が他の場所でより効率的に処理されるようになったため、削除されました。
  3. cgen_as関数のシグネチャ変更:

    • src/cmd/6g/gen.csrc/cmd/6g/gg.hにおいて、cgen_as関数のシグネチャがvoid cgen_as(Node *nl, Node *nr, int op)からvoid cgen_as(Node *nl, Node *nr)に変更されました。
    • 元のシグネチャでは、op引数が代入操作の種類(例: =+=など)を示していたと考えられます。このop引数が削除されたということは、代入操作の種類がnl(左辺)やnr(右辺)のNode構造体自体から推論されるようになったか、あるいはcgen_asが純粋な「単純代入」(=)のみを扱うようになり、複合代入(+=など)は別の関数(例: cgen_asop)で処理されるようになったことを意味します。これにより、関数の責務がより明確になり、コードの複雑性が軽減されます。
  4. OFORからOEXTENDへのオペコード変更:

    • src/cmd/6g/gen.csrc/cmd/6g/gsubr.coptoas関数内で、CASE(OFOR, TINT16)CASE(OFOR, TINT32)CASE(OFOR, TINT64)がそれぞれCASE(OEXTEND, TINT16)CASE(OEXTEND, TINT32)CASE(OEXTEND, TINT64)に変更されました。
    • これに伴い、src/cmd/gc/go.hに新しいオペコードOEXTENDが追加されました。
    • この変更は、コンパイラ内部で「型拡張」に関連する操作をより明確に表現するためのものです。OFORという汎用的な名前から、OEXTENDという具体的な操作を示す名前に変更することで、コンパイラの内部ロジックの意図がより明確になります。これは、特に異なるサイズの整数型間での変換(例: int8からint32への変換)において、符号拡張やゼロ拡張といった特定の命令が必要となる場合に重要です。

これらの変更は、Goコンパイラの初期段階におけるコードベースの健全性を高め、将来的な開発を容易にするための重要なステップでした。

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

src/cmd/6g/cgen.c (コメント追加の例)

--- a/src/cmd/6g/cgen.c
+++ b/src/cmd/6g/cgen.c
@@ -4,6 +4,10 @@
 
 #include "gg.h"
 
+/*
+ * generate:
+ *	res = n;
+ */
 void
 cgen(Node *n, Node *res)
 {
@@ -371,6 +375,10 @@ ret:
 	;
 }
 
+/*
+ * generate:
+ *	res = &n;
+ */
 void
 agen(Node *n, Node *res)
 {
@@ -616,6 +624,14 @@ fieldoffset(Type *t, Node *n)
 	return 0;
 }
 
+/*
+ * generate:
+ *	newreg = &n;
+ *	res = newreg
+ *
+ * on exit, a has been changed to be *newreg.
+ * caller must regfree(a).
+ */
 void
 igen(Node *n, Node *a, Node *res)
 {

src/cmd/6g/gen.c (デッドコード削除、関数シグネチャ変更、コメント追加、オペコード変更の例)

--- a/src/cmd/6g/gen.c
+++ b/src/cmd/6g/gen.c
@@ -8,12 +8,6 @@
 #include "gg.h"
 #include "opt.h"
 
-enum
-{
-	// random unused opcode
-	AJMPX	= AADDPD,
-};
-
 void
 compile(Node *fn)
 {
@@ -334,7 +328,7 @@ loop:
 		break;
 
 	case OAS:
-		cgen_as(n->left, n->right, n->op);
+		cgen_as(n->left, n->right);
 		break;
 
 	case OCALLMETH:
@@ -655,6 +649,9 @@ genpanic(void)
 	p->to.type = D_INDIR+D_AX;
 }
 
+/*
+ * compute total size of f's in/out arguments.
+ */
 int
 argsize(Type *t)
 {
@@ -1038,8 +1076,13 @@ ret:
 	;
 }
 
+/*
+ * generate assignment:
+ *	nl = nr
+ * nr == N means zero nl.
+ */
 void
-cgen_as(Node *nl, Node *nr, int op)
+cgen_as(Node *nl, Node *nr)
 {
 	Node nc, n1;
 	Type *tl;
@@ -1052,8 +1095,8 @@ cgen_as(Node *nl, Node *nr, int op)
 	iszer = 0;
 	if(nr == N || isnil(nr)) {
 		if(nl->op == OLIST) {
-			cgen_as(nl->left, nr, op);
-			cgen_as(nl->right, nr, op);
+			cgen_as(nl->left, nr);
+			cgen_as(nl->right, nr);
 			return;
 		}
 		tl = nl->type;
@@ -1193,7 +1236,7 @@ dodiv(int op, Node *nl, Node *nr, Node *res, Node *ax, Node *dx)
 			nodconst(&n4, t, 0);
 			gmove(&n4, dx);
 		} else
-		gins(optoas(OFOR, t), N, N);
+		gins(optoas(OEXTEND, t), N, N);
 		cgen(nr, &n3);
 	} else {
 		cgen(nr, &n3);
@@ -1202,7 +1245,7 @@ dodiv(int op, Node *nl, Node *nr, Node *res, Node *ax, Node *dx)
 			nodconst(&n4, t, 0);
 			gmove(&n4, dx);
 		} else
-		gins(optoas(OFOR, t), N, N);
+		gins(optoas(OEXTEND, t), N, N);
 	}
 	gins(a, &n3, N);
 	regfree(&n3);

src/cmd/6g/gg.h (関数プロトタイプ変更、デッドコード削除の例)

--- a/src/cmd/6g/gg.h
+++ b/src/cmd/6g/gg.h
@@ -145,7 +145,7 @@ void	swgen(Node*);
 void	selgen(Node*);
 Node*	lookdot(Node*, Node*, int);
 void	inarggen(void);
-void	cgen_as(Node*, Node*, int);
+void	cgen_as(Node*, Node*);
 void	cgen_asop(Node*);
 void	cgen_ret(Node*);
 void	cgen_call(Node*, int);
@@ -202,7 +202,6 @@ void	ginit(void);
 void	gclean(void);
 void	regalloc(Node*, Type*, Node*);
 void	regfree(Node*);
-void	regsalloc(Node*, Type*);	// replace w tmpvar
 void	regret(Node*, Type*);
 Node*	nodarg(Type*, int);
 void	nodreg(Node*, Type*, int);

src/cmd/6g/gsubr.c (コメント追加、デッドコード削除、オペコード変更の例)

--- a/src/cmd/6g/gsubr.c
+++ b/src/cmd/6g/gsubr.c
@@ -42,6 +42,10 @@ clearp(Prog *p)
 	pcloc++;
 }
 
+/*
+ * generate and return proc with p->as = as,
+ * linked into program.  pc is next instruction.
+ */
 Prog*
 prog(int as)
 {
@@ -1545,15 +1583,15 @@ optoas(int op, Type *t)
 		a = ADIVQ;
 		break;
 
-	case CASE(OFOR, TINT16):
+	case CASE(OEXTEND, TINT16):
 		a = ACWD;
 		break;
 
-	case CASE(OFOR, TINT32):
+	case CASE(OEXTEND, TINT32):
 		a = ACDQ;
 		break;
 
-	case CASE(OFOR, TINT64):
+	case CASE(OEXTEND, TINT64):
 		a = ACQO;
 		break;
 
@@ -1583,22 +1621,6 @@ isfat(Type *t)
 	return 0;
 }
 
-/*
- * return unsigned(op)
- * eg GT -> HS
- */
-int
-brunsigned(int a)
-{
-	switch(a) {
-	case AJLT:	return AJGE;
-	case AJGT:	return AJLE;
-	case AJLE:	return AJGT;
-	case AJGE:	return AJLT;
-	}
-	return a;
-}
-
 /*
  * return !(op)
  * eg == <=> !=

src/cmd/gc/go.h (新しいオペコードの追加)

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -313,6 +313,8 @@ enum
 	OLITERAL, OREGISTER, OINDREG,
 	OCONV, OCOMP, OKEY,
 	OBAD,
+	
+	OEXTEND,	// 6g internal
 
 	OEND,
 };

コアとなるコードの解説

コメントの追加

cgen.cgen.cgsubr.cに追加されたコメントは、Goコンパイラのコード生成ルーチンの動作を明確にしています。例えば、cgen(Node *n, Node *res)関数に「res = n; を生成する」というコメントが追加されたことで、この関数がGo言語の代入操作をアセンブリレベルでどのように実現するのかが直感的に理解できるようになりました。これは、コンパイラのデバッグや将来的な機能拡張において、開発者がコードの意図を素早く把握するために非常に重要です。

AJMPXの削除

src/cmd/6g/gen.cから削除されたAJMPXは、コメントにもあるように「ランダムな未使用オペコード」でした。これは、開発中に一時的に追加されたものの、最終的に使用されなかったか、あるいは別の方法で処理されるようになった命令のプレースホルダーであった可能性が高いです。このようなデッドコードを削除することで、コンパイラのバイナリサイズをわずかに削減し、コードベースの混乱を防ぎます。

cgen_as関数のシグネチャ変更

cgen_as関数のシグネチャからint op引数が削除されたことは、この関数がより特化した役割を持つようになったことを示唆しています。以前は、この関数が様々な種類の代入操作(単純代入、加算代入など)をop引数に基づいて処理していた可能性があります。しかし、変更後は、cgen_asは純粋な単純代入(nl = nr)のみを扱い、複合代入はcgen_asopのような別の関数で処理されるようになったと考えられます。これにより、各関数の責務が明確になり、コードのモジュール性が向上します。また、cgen_asの内部でOLIST(複数の代入をリストで処理する場合)の再帰呼び出しも、op引数なしの新しいシグネチャに合わせて修正されています。

regsalloc関数の削除

regsalloc関数は、gg.hからプロトタイプが、gsubr.cから定義が削除されました。この関数は、実装がfatal("regsalloc");となっており、実際に機能していなかったか、あるいはその機能が他のレジスタ割り当てメカニズムに統合されたため、デッドコードとして削除されました。これは、コンパイラのレジスタ割り当て戦略が進化し、より洗練された方法が採用された結果である可能性があります。

brunsigned関数の削除

brunsigned関数は、条件分岐命令の符号なしバージョンを返すことを意図していましたが、gsubr.cから削除されました。この関数が削除された理由は、その機能が不要になったか、あるいはより汎用的な方法で符号なし比較が処理されるようになったためと考えられます。例えば、特定の命令ではなく、オペランドの型情報に基づいて適切な比較命令が選択されるようになったなどが考えられます。

OFORからOEXTENDへのオペコード変更

optoas関数におけるOFORからOEXTENDへの変更は、コンパイラ内部のセマンティクスをより正確に反映するための重要なリファクタリングです。OFORは曖昧な名前でしたが、OEXTENDは「拡張」という具体的な操作(例: 符号拡張、ゼロ拡張)を指します。これは、Go言語の型システムにおける整数型の変換(例: int8からint32へのキャスト)が、アセンブリレベルで特定の拡張命令(例: ACWD, ACDQ, ACQO)にマッピングされることを明確に示しています。この変更により、コンパイラのコード生成ロジックがより理解しやすくなり、将来的な最適化やバグ修正が容易になります。

これらの変更は全体として、Goコンパイラの初期段階におけるコードベースの品質、可読性、保守性を向上させるための重要なクリーンアップ作業でした。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(GitHubリポジトリ): https://github.com/golang/go
  • 一般的なコンパイラ設計に関する資料(例: Dragon Book)
  • x86-64アセンブリ言語の命令セットリファレンス
  • Go言語の初期のコミット履歴と関連する議論(Goのメーリングリストなど)
    • Goの初期のコミットは、Google CodeのMercurialリポジトリからGitHubに移行されたものです。当時の議論は、Goのメーリングリストアーカイブなどで見つけることができます。
    • Go Developers Mailing List Archives: https://groups.google.com/g/golang-nuts
  • この解説は、提供されたコミット情報と、Go言語およびコンパイラ設計に関する一般的な知識に基づいて生成されています。