[インデックス 1341] ファイルの概要
このコミットは、Goコンパイラのコード生成部分におけるさらなる最適化とコードの整理を目的としています。特に、6g
(x86-64アーキテクチャ向けGoコンパイラ) のコード生成器と共通コンパイラ (gc
) の定数処理部分に焦点を当てています。
コミット
commit 42d89ac02ceeccbaf6973e8bd0636935398bd9f8
Author: Ken Thompson <ken@golang.org>
Date: Sun Dec 14 18:45:00 2008 -0800
even more code improvement
R=r
OCL=21160
CL=21160
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/42d89ac02ceeccbaf6973e8bd0636935398bd9f8
元コミット内容
このコミットは、Goコンパイラのコードベースに対する「さらなるコード改善」を意図しています。具体的な変更は、主にsrc/cmd/6g/gen.c
、src/cmd/6g/gsubr.c
、src/cmd/gc/const.c
、および関連するヘッダファイルに及びます。
変更の背景
Goコンパイラは、初期段階から継続的にパフォーマンスとコード品質の改善が行われてきました。このコミットは、特にコード生成の効率化と、コンパイラ内部の共通ユーティリティ関数の適切な配置を目的としています。
具体的な背景としては、以下の点が挙げられます。
- コード生成の最適化:
cgen_asop
関数における代入演算子(+=
,-=
など)の処理は、頻繁に発生する操作であり、その効率は生成されるバイナリのパフォーマンスに直結します。特に、定数1
の加算・減算はインクリメント/デクリメント命令に置き換えることで、より効率的なコードを生成できます。 - 共通関数の再配置:
smallintconst
のような関数は、特定のアーキテクチャ(6g
)に限定されず、コンパイラ全体で利用される可能性のあるユーティリティです。このような関数を共通のgc
パッケージに移動することで、コードの再利用性を高め、将来的なメンテナンスを容易にします。 - コードの可読性と保守性の向上:
ginit
およびgclean
関数におけるレジスタの初期化/クリーンアップ処理を、ハードコードされたリストから配列ベースのループ処理に移行することで、コードがより簡潔になり、新しいレジスタが追加された際の変更が容易になります。 - バグ修正と堅牢性の向上:
sudoaddable
関数におけるcleani
の初期化位置の変更や、oindex
におけるゼロ幅インデックスのfatal
エラーの削除は、特定のコーナーケースにおけるコンパイラの挙動を改善し、より堅牢にするためのものです。
これらの変更は、Goコンパイラがより高速で効率的なコードを生成し、同時にコンパイラ自体のコードベースがより整理され、保守しやすくなることを目指しています。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラの内部構造と関連するコンピュータサイエンスの概念に関する知識が必要です。
-
Goコンパイラの構造:
cmd/gc
: Goコンパイラの共通部分(gc
は"Go compiler"の略)。プラットフォーム非依存のフロントエンド処理(構文解析、型チェック、AST構築、最適化など)を担当します。cmd/6g
:gc
のバックエンドの一つで、x86-64アーキテクチャ(AMD64)向けのコード生成を担当します。6g
の6
は64-bitを意味します。gen.c
: コード生成器の主要部分。AST(抽象構文木)を走査し、ターゲットアーキテクチャの機械語命令(アセンブリ)を生成します。gsubr.c
:6g
コンパイラの汎用サブルーチンやヘルパー関数が含まれるファイル。const.c
:gc
における定数処理に関連する関数が含まれるファイル。go.h
/gg.h
: それぞれgc
と6g
のヘッダファイルで、データ構造や関数のプロトタイプが定義されています。
-
AST (Abstract Syntax Tree): ソースコードの構文構造を木構造で表現したもの。コンパイラはASTを操作してコードの解析、最適化、コード生成を行います。
Node
はASTのノードを表します。 -
Node
のプロパティ:op
: ノードが表す操作(例:OADD
は加算、OSUB
は減算、OLITERAL
はリテラル定数)。left
/right
: 二項演算子の場合の左右のオペランド。type
: ノードが表す式の型(例:int
,float64
)。addable
: そのノードがメモリ上のアドレスとして直接アクセス可能かどうかを示すフラグ。ullman
: Ullman数。レジスタ割り当てのヒューリスティックで使われる値で、式の評価に必要なレジスタの最小数を示す。UINF
は無限大(非常に大きい数)を意味し、複雑な式やメモリ上の値を示すことが多い。
-
アセンブリ命令とGoコンパイラの内部表現:
gins
: "generate instruction"の略で、アセンブリ命令を生成する関数。optoas
: Goコンパイラの内部演算子(OADD
,OINC
など)を、ターゲットアーキテクチャのアセンブリ命令(AADD
,AINC
など)に変換する関数。N
:Node
のNULLポインタ、またはオペランドがないことを示す。Prog
: 生成されるアセンブリ命令を表す構造体。Addr
: アセンブリ命令のオペランドのアドレスを表す構造体。regalloc
/regfree
: レジスタを割り当てたり解放したりする関数。コンパイラのレジスタ割り当て器の一部。D_AX
,D_CX
,D_DX
,D_SP
,D_R14
,D_R15
: x86-64アーキテクチャにおける特定のレジスタ(AX, CX, DX, SP, R14, R15)。これらは特定の操作(除算、シフト、スタックポインタ、GoランタイムのM/Pレジスタなど)で予約されることが多い。
-
sudoaddable
: "pseudo-addable"の略で、直接アドレス可能ではないが、一時的なレジスタやメモリ操作を介してアドレス可能にできるノードを処理するための関数。例えば、構造体のフィールドや配列の要素など。 -
OLITERAL
: コンパイル時に値が確定している定数。
これらの概念を理解することで、コミットがGoコンパイラのどの部分で、どのような目的で、どのように変更を加えているのかを深く把握できます。
技術的詳細
src/cmd/6g/gen.c
におけるcgen_asop
の改善
cgen_asop
関数は、a += b
のような代入演算子を含む式のコード生成を担当します。このコミットでは、特にOADD
(加算代入)とOSUB
(減算代入)の最適化が強化されています。
変更前は、nl->addable && nr->op == OLITERAL
という条件で、右辺がリテラル定数である場合にのみ特定の最適化(OINC
/ODEC
への変換)を試みていました。しかし、この変更により、以下の点が改善されました。
- Ullman数に基づく最適化の優先順位付け:
if(nr->ullman >= UINF && nl->ullman >= UINF)
という新しいチェックが追加されました。これは、左右のオペランドが両方とも複雑な式(Ullman数が高い)である場合に、まず右辺を一時変数に評価してから代入演算を行うという戦略を取ることを示唆しています。これにより、レジスタの競合を減らし、より効率的なコードパスを選択できます。
OINC
/ODEC
命令への変換の強化:OADD
およびOSUB
の場合に、右辺がsmallintconst(nr)
(小さな整数定数)であり、かつその値が1
である場合に、OINC
(インクリメント)またはODEC
(デクリメント)命令に変換するロジックが追加されました。- さらに、左辺が
addable
(直接アドレス可能)であるか、またはsudoaddable
(一時的な操作でアドレス可能)であるかに応じて、直接インクリメント/デクリメント命令を生成するか、または一時レジスタを介して操作するかが選択されます。これは、x++
やx--
のような操作を効率的にアセンブリに変換するための重要な最適化です。 sudoaddable
が成功した場合、gins
で生成されたProg
構造体のto
フィールドにaddr
を設定し、sudoclean()
を呼び出して一時的なリソースを解放します。
- 汎用的な代入演算の処理:
OXOR
,OAND
,OOR
などのビット演算子を含む代入、およびOADD
,OSUB
のより一般的なケース(右辺が1
でない場合など)についても、左辺がaddable
であるか、またはsudoaddable
であるかに応じて、レジスタ割り当てとgins
による命令生成のロジックが整理されました。- 右辺がリテラル定数である場合は、直接その定数をオペランドとして使用する最適化(
gins(optoas(n->etype, nl->type), nr, nl);
)が追加されました。
これらの変更により、cgen_asop
はより多くのケースで最適化されたアセンブリコードを生成できるようになり、特にインクリメント/デクリメント操作の効率が向上しました。
smallintconst
関数の移動
smallintconst
関数は、与えられたNode
が小さな整数定数(int8
, uint8
, int16
, uint16
, int32
, uint32
, bool
, ptr32
)を表すリテラルであるかどうかを判定するユーティリティ関数です。
このコミットでは、この関数がsrc/cmd/6g/gsubr.c
からsrc/cmd/gc/const.c
に移動されました。これに伴い、プロトタイプ宣言もsrc/cmd/6g/gg.h
からsrc/cmd/gc/go.h
に移動されています。
この移動の理由は、smallintconst
が6g
(x86-64バックエンド)に特化した機能ではなく、コンパイラの共通部分(gc
)で定数を扱う際に広く利用できる汎用的なユーティリティであるためです。これにより、コードのモジュール性が向上し、将来的に他のアーキテクチャのバックエンドでもこの関数を容易に利用できるようになります。
src/cmd/6g/gsubr.c
におけるレジスタ初期化の改善
ginit
関数は、コンパイラの初期化時にレジスタの状態をセットアップします。変更前は、特定の予約済みレジスタ(D_AX
, D_CX
, D_DX
, D_SP
, D_R14
, D_R15
)に対して個別にreg[D_XX]++
という形で参照カウントをインクリメントしていました。
このコミットでは、これらの予約済みレジスタをresvd
という静的配列にまとめ、ループを使ってreg[resvd[i]]++
という形で処理するように変更されました。同様に、gclean
関数でもreg[resvd[i]]--
という形でループ処理に置き換えられました。
この変更により、コードの重複が排除され、可読性が向上しました。また、将来的に予約済みレジスタが追加または削除された場合でも、resvd
配列を修正するだけで済むため、メンテナンスが容易になります。
sudoaddable
関数の改善
sudoaddable
関数は、与えられたNode
が、一時的なレジスタやメモリ操作を介してアドレス可能にできるかどうかを判定し、そのためのコードを生成する準備をする関数です。
このコミットでは、sudoaddable
関数内のcleani
(クリーンアップスロットのインデックス)とreg
, reg1
(一時レジスタノード)の初期化ロジックが変更されました。変更前は関数の冒頭で無条件に初期化されていましたが、変更後はODOT
(構造体フィールドアクセス)とOINDEX
(配列要素アクセス)のcase
ブロック内に移動されました。
これは、cleani
と一時レジスタの割り当てが、これらの特定のノードタイプ(構造体フィールドや配列要素)をアドレス可能にする場合にのみ必要であることを示唆しています。これにより、不要なリソースの割り当てを避け、関数の効率が向上します。
また、oindex
ラベルの直前で、w = n->type->width;
の後にあったif(w == 0) fatal("index is zero width");
というゼロ幅インデックスに対する致命的エラーのチェックが削除されました。これは、ゼロ幅の型(例えば空の構造体)に対するインデックス操作が、コンパイラの他の部分で適切に処理されるようになったか、あるいはそのようなケースがもはや発生しないように保証されるようになったことを示唆しています。
コアとなるコードの変更箇所
src/cmd/6g/gen.c
(抜粋)
--- a/src/cmd/6g/gen.c
+++ b/src/cmd/6g/gen.c
@@ -915,55 +915,96 @@ cgen_asop(Node *n)
{
Node n1, n2, n3, n4;
Node *nl, *nr;
+ Prog *p1;
+ Addr addr;
nl = n->left;
nr = n->right;
- if(nl->addable && nr->op == OLITERAL)
+ if(nr->ullman >= UINF && nl->ullman >= UINF) {
+ tempname(&n1, nr->type);
+ cgen(nr, &n1);
+ n2 = *n;
+ n2.right = &n1;
+ cgen_asop(&n2);
+ goto ret;
+ }
+
+ if(!isint[nl->type->etype])
+ goto hard;
+ if(!isint[nr->type->etype])
+ goto hard;
+
switch(n->etype) {
case OADD:
-- if(!isint[nl->type->etype])
-- break;
-- if(mpgetfix(nr->val.u.xval) != 1)
-- break;
-- gins(optoas(OINC, nl->type), N, nl);
-- goto ret;
-+ if(smallintconst(nr))
-+ if(mpgetfix(nr->val.u.xval) == 1) {
-+ if(nl->addable) {
-+ gins(optoas(OINC, nl->type), N, nl);
-+ goto ret;
-+ }
-+ if(sudoaddable(nl, nr->type, &addr)) {
-+ p1 = gins(optoas(OINC, nl->type), N, N);
-+ p1->to = addr;
-+ sudoclean();
-+ goto ret;
-+ }
-+ }
-+ break;
-+
case OSUB:
-- if(!isint[nl->type->etype])
-- break;
-- if(mpgetfix(nr->val.u.xval) != 1)
-- break;
-- gins(optoas(ODEC, nl->type), N, nl);
-- goto ret;
-+ if(smallintconst(nr))
-+ if(mpgetfix(nr->val.u.xval) == 1) {
-+ if(nl->addable) {
-+ gins(optoas(ODEC, nl->type), N, nl);
-+ goto ret;
-+ }
-+ if(sudoaddable(nl, nr->type, &addr)) {
-+ p1 = gins(optoas(ODEC, nl->type), N, N);
-+ p1->to = addr;
-+ sudoclean();
-+ goto ret;
-+ }
-+ }
-+ break;
}
-- if(nl->addable)
switch(n->etype) {
+ case OADD:
+ case OSUB:
case OXOR:
case OAND:
case OOR:
-- case OADD:
-- case OSUB:
-- if(!isint[nl->type->etype])
-- break;
-- if(!isint[nr->type->etype])
-- break;
-- regalloc(&n2, nr->type, N);
-- cgen(nr, &n2);
-- gins(optoas(n->etype, nl->type), &n2, nl);
-- regfree(&n2);
-- goto ret;
-+ if(nl->addable) {
-+ if(smallintconst(nr)) {
-+ gins(optoas(n->etype, nl->type), nr, nl);
-+ goto ret;
-+ }
-+ regalloc(&n2, nr->type, N);
-+ cgen(nr, &n2);
-+ gins(optoas(n->etype, nl->type), &n2, nl);
-+ regfree(&n2);
-+ goto ret;
-+ }
-+ if(nr->ullman < UINF)
-+ if(sudoaddable(nl, nr->type, &addr)) {
-+ if(smallintconst(nr)) {
-+ p1 = gins(optoas(n->etype, nl->type), nr, N);
-+ p1->to = addr;
-+ sudoclean();
-+ goto ret;
-+ }
-+ regalloc(&n2, nr->type, N);
-+ cgen(nr, &n2);
-+ p1 = gins(optoas(n->etype, nl->type), &n2, N);
-+ p1->to = addr;
-+ regfree(&n2);
-+ sudoclean();
-+ goto ret;
-+ }
}
-- if(nr->ullman >= UINF && nl->ullman >= UINF) {
-- tempname(&n1, nr->type);
-- cgen(nr, &n1);
-- n2 = *n;
-- n2.right = &n1;
-- cgen_asop(&n2);
-- goto ret;
-+hard:
if(nr->ullman > nl->ullman) {
regalloc(&n2, nr->type, N);
cgen(nr, &n2);
src/cmd/6g/gg.h
(抜粋)
--- a/src/cmd/6g/gg.h
+++ b/src/cmd/6g/gg.h
@@ -199,7 +199,6 @@ void
tempname(Node*, Type*);
Plist* newplist(void);
int isfat(Type*);
void setmaxarg(Type*);
-int smallintconst(Node*);
void sudoclean(void);
int sudoaddable(Node*, Type*, Addr*);
src/cmd/6g/gsubr.c
(抜粋)
--- a/src/cmd/6g/gsubr.c
+++ b/src/cmd/6g/gsubr.c
@@ -102,6 +102,19 @@ newplist(void)
return pl;
}
+static int resvd[] =
+{
+// D_DI, // for movstring
+// D_SI, // for movstring
+
+ D_AX, // for divide
+ D_CX, // for shift
+ D_DX, // for divide
+ D_SP, // for stack
+ D_R14, // reserved for m
+ D_R15, // reserved for u
+};
+
void
ginit(void)
{
@@ -114,15 +127,8 @@ ginit(void)
for(i=D_X0; i<=D_X7; i++)
reg[i] = 0;
-// reg[D_DI]++; // for movstring
-// reg[D_SI]++; // for movstring
-
- reg[D_AX]++; // for divide
- reg[D_CX]++; // for shift
- reg[D_DX]++; // for divide
- reg[D_SP]++; // for stack
- reg[D_R14]++; // reserved for m
- reg[D_R15]++; // reserved for u
+ for(i=0; i<nelem(resvd); i++)
+ reg[resvd[i]]++;
}
void
@@ -130,15 +136,8 @@ gclean(void)
{
int i;
-// reg[D_DI]--; // for movstring
-// reg[D_SI]--; // for movstring
-
- reg[D_AX]--; // for divide
- reg[D_CX]--; // for shift
- reg[D_DX]--; // for divide
- reg[D_SP]--; // for stack
- reg[D_R14]--; // reserved for m
- reg[D_R15]--; // reserved for u
+ for(i=0; i<nelem(resvd); i++)
+ reg[resvd[i]]--;
for(i=D_AX; i<=D_R15; i++)
if(reg[i])
@@ -1817,24 +1816,6 @@ dotoffset(Node *n, int *oary, Node **nn)
return i;
}
--int
--smallintconst(Node *n)
--{
-- if(n->op == OLITERAL)
-- switch(simtype[n->type->etype]) {
-- case TINT8:
-- case TUINT8:
-- case TINT16:
-- case TUINT16:
-- case TINT32:
-- case TUINT32:
-- case TBOOL:
-- case TPTR32:
-- return 1;
-- }
-- return 0;
--}
--
enum
{
ODynam = 1<<0,
@@ -1857,37 +1838,36 @@ sudoclean(void)
int
sudoaddable(Node *n, Type *t, Addr *a)
{
-- int et, o, i, w;
-+ int o, i, w;
int oary[10];
vlong v;
-- Node n0, n1, n2, *nn, *l, *r;
-+ Node n1, n2, *nn, *l, *r;
Node *reg, *reg1;
Prog *p1;
-- // make a cleanup slot
-- cleani += 2;
-- reg = &clean[cleani-1];
-- reg1 = &clean[cleani-2];
-- reg->op = OEMPTY;
-- reg1->op = OEMPTY;
--
if(n->type == T || t == T)
-- goto no;
--et = simtype[n->type->etype];
--if(et != simtype[t->etype])
-- goto no;
-+ return 0;
switch(n->op) {
default:
-- goto no;
-+ return 0;
case ODOT:
case ODOTPTR:
-+ cleani += 2;
-+ reg = &clean[cleani-1];
-+ reg1 = &clean[cleani-2];
-+ reg->op = OEMPTY;
-+ reg1->op = OEMPTY;
goto odot;
case OINDEXPTR:
-- goto no;
case OINDEX:
-+ cleani += 2;
-+ reg = &clean[cleani-1];
-+ reg1 = &clean[cleani-2];
-+ reg->op = OEMPTY;
-+ reg1->op = OEMPTY;
goto oindex;
}
@@ -1895,15 +1875,6 @@ odot:
o = dotoffset(n, oary, &nn);
if(nn == N)
goto no;
--
-- if(0) {
-- dump("\nXX", n);
-- dump("YY", nn);
-- for(i=0; i<o; i++)
-- print(" %d", oary[i]);
-- print("\n");
-- goto no;
-- }
regalloc(reg, types[tptr], N);
n1 = *reg;
@@ -1931,7 +1902,7 @@ odot:
oindex:
l = n->left;
r = n->right;
-- if(l->ullman >= UINF || r->ullman >= UINF)
-+ if(l->ullman >= UINF && r->ullman >= UINF)
goto no;
// set o to type of array
@@ -1950,8 +1921,6 @@ oindex:
}
w = n->type->width;
-- if(w == 0)
-- fatal("index is zero width");
if(whatis(r) == Wlitint)
goto oindex_const;
src/cmd/gc/const.c
(抜粋)
--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -540,3 +540,21 @@ cmpslit(Node *l, Node *r)
return +1;
return -1;
}
+
+int
+smallintconst(Node *n)
+{
+ if(n->op == OLITERAL)
+ switch(simtype[n->type->etype]) {
+ case TINT8:
+ case TUINT8:
+ case TINT16:
+ case TUINT16:
+ case TINT32:
+ case TUINT32:
+ case TBOOL:
+ case TPTR32:
+ return 1;
+ }
+ return 0;
+}
src/cmd/gc/go.h
(抜粋)
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -826,6 +826,7 @@ void convlit1(Node*, Type*, int);
void convlit(Node*, Type*);
void evconst(Node*);
int cmpslit(Node *l, Node *r);
+int smallintconst(Node*);
/*
* gen.c/gsubr.c/obj.c
コアとなるコードの解説
src/cmd/6g/gen.c
のcgen_asop
- Ullman数による分岐: 変更前は
nl->addable && nr->op == OLITERAL
という単純な条件でしたが、変更後はnr->ullman >= UINF && nl->ullman >= UINF
という条件が追加されました。これは、左右のオペランドが両方とも複雑な式である場合に、右辺を一時変数に評価してから処理するという、より一般的な最適化パスを優先的に試みることを意味します。 OINC
/ODEC
最適化の強化:OADD
とOSUB
のケースで、右辺がsmallintconst
かつ値が1
の場合に特化した最適化が導入されました。nl->addable
であれば直接gins(optoas(OINC, ...), N, nl)
でインクリメント/デクリメント命令を生成します。sudoaddable(nl, nr->type, &addr)
が成功すれば、p1 = gins(optoas(OINC, ...), N, N); p1->to = addr; sudoclean();
という形で、アドレス可能にした上でインクリメント/デクリメント命令を生成します。これにより、x += 1
やx -= 1
のようなコードが、より効率的なINC
やDEC
アセンブリ命令に変換される可能性が高まります。
- 汎用的な代入演算の処理:
OXOR
,OAND
,OOR
,OADD
,OSUB
といった演算子に対して、左辺がaddable
であるか、またはsudoaddable
であるかに応じて、レジスタ割り当てと命令生成のロジックが整理されました。- 特に、
smallintconst(nr)
の場合には、レジスタを介さずに直接リテラル定数をオペランドとして使用するgins(optoas(n->etype, nl->type), nr, nl)
のような最適化が適用されます。これは、x += 5
のような場合に、ADD $5, x
のような命令を直接生成できることを意味します。
src/cmd/6g/gg.h
とsrc/cmd/gc/go.h
src/cmd/6g/gg.h
からsmallintconst
のプロトタイプ宣言が削除され、src/cmd/gc/go.h
に追加されました。これは、smallintconst
が6g
固有の関数ではなく、コンパイラ共通のユーティリティであることを明確にするための変更です。
src/cmd/6g/gsubr.c
smallintconst
の実装削除:smallintconst
関数の実装がこのファイルから完全に削除されました。- 予約済みレジスタの配列化:
ginit
とgclean
関数内で、予約済みレジスタ(D_AX
,D_CX
,D_DX
,D_SP
,D_R14
,D_R15
)の参照カウント操作が、resvd
という静的配列とループ処理に置き換えられました。これにより、コードの重複が解消され、レジスタの追加・削除時のメンテナンス性が向上しました。 sudoaddable
の初期化ロジック変更:sudoaddable
関数内で、cleani
と一時レジスタreg
,reg1
の初期化が、ODOT
とOINDEX
のcase
ブロック内に移動されました。これにより、これらのリソースが実際に必要となる場合にのみ割り当てられるようになり、効率が向上します。- ゼロ幅インデックスの
fatal
エラー削除:oindex
セクションからif(w == 0) fatal("index is zero width");
が削除されました。これは、ゼロ幅の型に対するインデックス操作が、コンパイラの他の部分で適切に処理されるようになったか、あるいはそのようなケースがもはや発生しないように保証されるようになったことを示唆しています。
src/cmd/gc/const.c
smallintconst
の実装追加:smallintconst
関数の実装がこのファイルに追加されました。これにより、この関数がgc
(共通コンパイラ)の一部として利用可能になり、Goコンパイラ全体で一貫した小さな整数定数の判定ロジックが提供されます。
これらの変更は、Goコンパイラのコード生成の効率を向上させるとともに、コンパイラ自体のコードベースの構造を改善し、より保守しやすく、拡張しやすいものにすることを目的としています。
関連リンク
- Go言語の公式ウェブサイト: https://go.dev/
- Goコンパイラのソースコード(GitHub): https://github.com/golang/go
- Goコンパイラの内部構造に関するドキュメント(古いものも含むが参考になる):
- "Go Compiler Internals" by Russ Cox: https://go.dev/doc/articles/go_compiler_internals.html
- "A Tour of the Go Compiler" by Russ Cox: https://go.dev/talks/2015/go-compiler.slide
参考にした情報源リンク
- Goコンパイラのソースコード(上記GitHub URL)
- Go言語のドキュメント
- コンパイラ最適化に関する一般的な知識(Ullman数、レジスタ割り当てなど)
- x86-64アセンブリ言語の知識
- Go言語の初期のコミット履歴と関連する議論(Goプロジェクトのメーリングリストなど)
- Go Project Mailing List Archives: https://groups.google.com/g/golang-nuts (具体的なスレッドは特定していませんが、当時の議論の雰囲気を理解するのに役立ちます)
- Goコンパイラのコードベースを理解するための一般的なプログラミング知識とシステムプログラミングの概念。