[インデックス 14003] ファイルの概要
このコミットは、Go言語の32ビットx86アーキテクチャ向けコンパイラであるcmd/8g
における最適化を導入しています。具体的には、文字列やスライスの要素のアドレスを取得する操作(例: &s[i]
)において、不要なアドレス計算を削減し、生成される機械語コードの効率を向上させています。この変更により、LEAL
(Load Effective Address) 命令の使用回数が減少し、複数のベンチマークでパフォーマンスの改善が見られます。
コミット
commit 2de064b63c6d0eb5cba5bd877da81c8100aaeb2a
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Tue Oct 2 08:19:27 2012 +0200
cmd/8g: do not take the address of string/slice for &s[i]
A similar change was made in 6g recently.
LEALs in cmd/go: 31440 before, 27867 after.
benchmark old ns/op new ns/op delta
BenchmarkBinaryTree17 7065794000 6723617000 -4.84%
BenchmarkFannkuch11 7767395000 7477945000 -3.73%
BenchmarkGobDecode 34708140 34857820 +0.43%
BenchmarkGobEncode 10998780 10960060 -0.35%
BenchmarkGzip 1603630000 1471052000 -8.27%
BenchmarkGunzip 242573900 240650400 -0.79%
BenchmarkJSONEncode 120842200 117966100 -2.38%
BenchmarkJSONDecode 247254900 249103100 +0.75%
BenchmarkMandelbrot200 29237330 29241790 +0.02%
BenchmarkParse 8111320 8096865 -0.18%
BenchmarkRevcomp 2595780000 2694153000 +3.79%
BenchmarkTemplate 276679600 264497000 -4.40%
benchmark old ns/op new ns/op delta
BenchmarkAppendFloatDecimal 429 416 -3.03%
BenchmarkAppendFloat 780 740 -5.13%
BenchmarkAppendFloatExp 746 700 -6.17%
BenchmarkAppendFloatNegExp 752 694 -7.71%
BenchmarkAppendFloatBig 1228 1108 -9.77%
BenchmarkAppendFloat32Integer 457 416 -8.97%
BenchmarkAppendFloat32ExactFraction 662 631 -4.68%
BenchmarkAppendFloat32Point 771 735 -4.67%
BenchmarkAppendFloat32Exp 722 672 -6.93%
BenchmarkAppendFloat32NegExp 724 659 -8.98%
BenchmarkAppendFloat64Fixed1 429 400 -6.76%
BenchmarkAppendFloat64Fixed2 463 442 -4.54%
Update #1914.
R=golang-dev, daniel.morsing, rsc
CC=golang-dev
https://golang.org/cl/6574043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2de064b63c6d0eb5cba5bd877da81c8100aaeb2a
元コミット内容
このコミットは、Goコンパイラcmd/8g
において、文字列やスライスの要素のアドレスを取得する際のコード生成ロジックを改善するものです。具体的には、&s[i]
のような操作において、コンパイラが不必要に文字列やスライス全体のベースアドレスを取得するのではなく、直接要素のアドレスを計算するように変更されています。これにより、生成されるアセンブリコードの効率が向上し、特にLEAL
命令の使用回数が削減されます。
変更の背景
Go言語のコンパイラは、生成されるバイナリのパフォーマンスを最大化するために継続的に最適化が行われています。このコミットの背景には、&s[i]
のような一般的な操作において、コンパイラが冗長なアドレス計算を行っているという認識がありました。特に、x86アーキテクチャのLEAL
命令は、アドレス計算だけでなく汎用的な算術演算にも使用されますが、その使用が最適でない場合、パフォーマンスのボトルネックとなる可能性があります。
コミットメッセージに「A similar change was made in 6g recently.」とあるように、64ビットx86アーキテクチャ向けの6g
コンパイラでも同様の最適化が先行して行われており、その成功を受けて8g
にも適用されたと考えられます。これは、Goコンパイラ開発チームが、異なるアーキテクチャのコンパイラ間で共通の最適化戦略を適用し、全体的なコード生成品質を向上させようとしていることを示唆しています。
具体的なパフォーマンス指標としてLEALs in cmd/go: 31440 before, 27867 after.
と示されているように、この最適化によってLEAL
命令の生成数が約11%削減されており、これがベンチマーク結果に現れるパフォーマンス向上に直結しています。
前提知識の解説
Goコンパイラ (cmd/8g
, cmd/6g
)
Go言語のコンパイラは、ターゲットアーキテクチャごとに異なる実装を持っています。
cmd/8g
: 32ビットx86 (Intel/AMD) アーキテクチャ向けのGoコンパイラです。Go 1.x系の時代には、8g
はgo build
コマンドの内部で32ビット環境向けにコードを生成する役割を担っていました。cmd/6g
: 64ビットx86 (Intel/AMD) アーキテクチャ向けのGoコンパイラです。現代のGo開発で最も一般的に使用されるコンパイラの一つです。
これらのコンパイラは、Goのソースコードを中間表現に変換し、最終的にターゲットアーキテクチャの機械語コードを生成します。コード生成の段階で、様々な最適化が適用されます。
&s[i]
操作
Go言語において、s
がスライスまたは文字列である場合、s[i]
はインデックスi
にある要素を参照します。&s[i]
は、その要素のメモリ上のアドレスを取得する操作です。例えば、byte
スライスb
に対して&b[0]
は、スライスの最初の要素へのポインタを返します。
LEAL
(Load Effective Address) 命令
LEAL
はx86アセンブリ言語の命令で、「Load Effective Address」の略です。この命令は、メモリからデータをロードするのではなく、オペランドで指定されたアドレスを計算し、その結果をレジスタに格納します。
例: LEAL (%EAX, %EBX, 4), %ECX
これは、ECX = EAX + EBX * 4
という計算を行い、結果をECX
レジスタに格納します。
LEAL
は、以下のような特徴からコンパイラ最適化の対象となりやすい命令です。
- アドレス計算: メモリ上の特定のアドレスを計算するために使用されます。
- 汎用的な算術演算: メモリへのアクセスを伴わないため、
ADD
やMUL
といった算術命令の組み合わせよりも効率的に複数の算術演算(加算、乗算、シフト)を一度に行うことができます。例えば、x * 4 + y
のような計算を効率的に実行できます。 - フラグへの影響なし:
LEAL
命令はCPUのフラグレジスタ(ゼロフラグ、キャリーフラグなど)を変更しません。これにより、後続の条件分岐などに影響を与えずにアドレス計算を行うことができます。
コンパイラは、コード内のアドレス計算パターンを認識し、可能な限りLEAL
命令を使用して最適化を図ります。しかし、不必要なLEAL
の使用は、レジスタの割り当てやパイプラインの効率に悪影響を与える可能性があるため、その使用を最適化することは重要です。
ベンチマーク
Go言語には、標準でベンチマーク機能が組み込まれています。go test -bench=.
コマンドを実行することで、コードのパフォーマンスを測定できます。ベンチマーク結果は通常、操作あたりのナノ秒(ns/op)で表示され、数値が小さいほど高速であることを示します。このコミットのベンチマーク結果は、変更前(old ns/op)と変更後(new ns/op)のパフォーマンスを比較し、delta
(変化率)で改善度合いを示しています。負のデルタはパフォーマンスの向上を意味します。
技術的詳細
このコミットの技術的詳細は、src/cmd/8g/cgen.c
ファイル内のコード生成ロジックの変更に集約されます。
cgenindex
からigenindex
への変更
まず、cgenindex
という関数がigenindex
にリネームされています。この関数は、配列のインデックスを処理し、そのインデックスに対応するアドレス可能なノード(メモリ上の位置を指す情報)を生成する役割を担っています。
変更前は、cgenindex
が常にインデックスn
に対してcgen(n, res)
を呼び出し、結果をres
に格納していました。cgen
は汎用的なコード生成関数であり、n
が既にアドレス可能である場合でも、冗長なコードを生成する可能性がありました。
変更後のigenindex
では、if(n->addable)
という条件が追加されています。これは、入力ノードn
が既にアドレス可能(つまり、その値がメモリ上の特定の位置に直接対応している)であるかどうかをチェックします。
- もし
n
が既にアドレス可能であれば、*res = *n;
として、res
にn
の情報を直接コピーします。これにより、不必要なコード生成をスキップできます。 n
がアドレス可能でない場合は、一時的な名前を割り当ててからcgen(n, res)
を呼び出し、コードを生成します。
この変更は、インデックスが既にレジスタにロードされている場合など、不必要なメモリ操作やアドレス計算を避けるための最適化です。
agen
関数における&s[i]
の最適化
agen
関数は、Goコンパイラにおけるアドレス生成の主要な関数です。この関数は、与えられたノードn
のアドレスを計算し、その結果をres
ノードに格納します。&s[i]
のような操作は、このagen
関数内で処理されます。
主要な変更点は以下の通りです。
-
agenr
関数の削除: 以前はagenr
というヘルパー関数が存在し、アドレスをレジスタに生成する役割を担っていました。このコミットではagenr
が削除され、その機能がagen
関数内に統合またはより効率的な方法で処理されるようになりました。これは、コードの簡素化と、より直接的なコード生成パスの実現に貢献しています。 -
インデックス処理の改善:
- 以前は、インデックス
nr
が定数でない場合、一時的なノードtmp
を作成し、cgenindex(nr, &tmp)
を呼び出してインデックスの値をtmp
に格納していました。 - 変更後では、
igenindex(nr, &tmp)
を呼び出すことで、インデックスが既にアドレス可能であればその情報を直接利用し、そうでなければ必要なコードを生成するように改善されています。これにより、インデックス値の取得がより効率的になります。
- 以前は、インデックス
-
ベースポインタの直接的なロード:
- スライスや文字列の要素のアドレスを計算する際、以前は
Array_array
フィールド(データ配列の開始アドレス)を介してベースポインタを取得していました。 - 変更後では、
// Load base pointer in n3.
というコメントが示すように、ベースポインタを直接n3
(一時的なノード)にロードするロジックが明確化されています。特に、固定長配列の場合には、agen(&n3, &n2)
のように、配列自体のベースアドレスを直接取得する処理が追加されています。
- スライスや文字列の要素のアドレスを計算する際、以前は
-
LEAL
命令の最適化された使用:w
(要素の幅)が1, 2, 4, 8バイトの場合、LEAL (n3)(n2*w), n3
という形式のLEAL
命令が生成されるようになりました。これは、ベースアドレスn3
にインデックスn2
と要素幅w
を乗算したオフセットを加算し、その結果を再びn3
に格納するという効率的なアドレス計算です。- 以前は、
w
が0でない場合に、OMUL
(乗算)とOADD
(加算)を個別に生成していた可能性があります。LEAL
命令は、これらの操作を単一の命令で実行できるため、命令数の削減と実行効率の向上に寄与します。 LEAL
命令のscale
フィールド(乗数)を直接設定することで、コンパイラがより最適化されたアドレス計算命令を生成できるようになります。
これらの変更により、&s[i]
のような操作が、より少ない命令数で、かつより効率的なx86アセンブリ命令(特にLEAL
)を使用してコンパイルされるようになります。これにより、実行時のパフォーマンスが向上します。
コアとなるコードの変更箇所
src/cmd/8g/cgen.c
--- a/src/cmd/8g/cgen.c
+++ b/src/cmd/8g/cgen.c
@@ -456,23 +456,30 @@ flt2: // binary
}
/*
- * generate array index into res.
- * n might be any size; res is 32-bit.
+ * generate an addressable node in res, containing the value of n.
+ * n is an array index, and might be any size; res width is <= 32-bit.
* returns Prog* to patch to panic call.
*/
Prog*
-cgenindex(Node *n, Node *res)
+igenindex(Node *n, Node *res)
{
Node tmp, lo, hi, zero;
if(!is64(n->type)) {
- cgen(n, res);
+ if(n->addable) {
+ // nothing to do.
+ *res = *n;
+ } else {
+ tempname(res, types[TUINT32]);
+ cgen(n, res);
+ }
return nil;
}
tempname(&tmp, types[TINT64]);
cgen(n, &tmp);
split64(&tmp, &lo, &hi);
+ tempname(res, types[TUINT32]);
gmove(&lo, res);
if(debug['B']) {
splitclean();
@@ -492,7 +499,7 @@ void
agen(Node *n, Node *res)
{
Node *nl, *nr;
- Node n1, n2, n3, n4, tmp;
+ Node n1, n2, n3, n4, tmp, nlen;
Type *t;
uint32 w;
uint64 v;
@@ -574,109 +581,117 @@ agen(Node *n, Node *res)
p2 = nil; // to be patched to panicindex.
w = n->type->width;
if(nr->addable) {
- if(!isconst(nr, CTINT))
- tempname(&tmp, types[TINT32]);
+ // Generate &nl first, and move nr into register.
if(!isconst(nl, CTSTR))
- agenr(nl, &n3, res);
+ igen(nl, &n3, res);
if(!isconst(nr, CTINT)) {
- p2 = cgenindex(nr, &tmp);
+ p2 = igenindex(nr, &tmp);
regalloc(&n1, tmp.type, N);
gmove(&tmp, &n1);
}
} else if(nl->addable) {
+ // Generate nr first, and move &nl into register.
if(!isconst(nr, CTINT)) {
- tempname(&tmp, types[TINT32]);
- p2 = cgenindex(nr, &tmp);
+ p2 = igenindex(nr, &tmp);
regalloc(&n1, tmp.type, N);
gmove(&tmp, &n1);
}
- if(!isconst(nl, CTSTR)) {
- regalloc(&n3, types[tptr], res);
- agen(nl, &n3);
- }
+ if(!isconst(nl, CTSTR))
+ igen(nl, &n3, res);
} else {
- tempname(&tmp, types[TINT32]);
- p2 = cgenindex(nr, &tmp);
+ p2 = igenindex(nr, &tmp);
nr = &tmp;
if(!isconst(nl, CTSTR))
- agenr(nl, &n3, res);
+ igen(nl, &n3, res);
regalloc(&n1, tmp.type, N);
gins(optoas(OAS, tmp.type), &tmp, &n1);
}
- // &a is in &n3 (allocated in res)
- // i is in &n1 (if not constant)
+ // For fixed array we really want the pointer in n3.
+ if(isfixedarray(nl->type)) {
+ regalloc(&n2, types[tptr], &n3);
+ agen(&n3, &n2);
+ regfree(&n3);
+ n3 = n2;
+ }
+
+ // &a[0] is in n3 (allocated in res)
+ // i is in n1 (if not constant)
+ // len(a) is in nlen (if needed)
// w is width
// explicit check for nil if array is large enough
// that we might derive too big a pointer.
if(isfixedarray(nl->type) && nl->type->width >= unmappedzero) {
- regalloc(&n4, types[tptr], &n3);
- gmove(&n3, &n4);
+ n4 = n3;
n4.op = OINDREG;
n4.type = types[TUINT8];
n4.xoffset = 0;
gins(ATESTB, nodintconst(0), &n4);
- regfree(&n4);
}
// constant index
if(isconst(nr, CTINT)) {
if(isconst(nl, CTSTR))
- fatal("constant string constant index");
+ fatal("constant string constant index"); // front end should handle
v = mpgetfix(nr->val.u.xval);
if(isslice(nl->type) || nl->type->etype == TSTRING) {
if(!debug['B'] && !n->bounded) {
- n1 = n3;
- n1.op = OINDREG;
- n1.type = types[tptr];
- n1.xoffset = Array_nel;
+ nlen = n3;
+ nlen.type = types[TUINT32];
+ nlen.xoffset += Array_nel;
nodconst(&n2, types[TUINT32], v);
- gins(optoas(OCMP, types[TUINT32]), &n1, &n2);
+ gins(optoas(OCMP, types[TUINT32]), &nlen, &n2);
p1 = gbranch(optoas(OGT, types[TUINT32]), T, +1);
ginscall(panicindex, -1);
patch(p1, pc);
}
-
- n1 = n3;
- n1.op = OINDREG;
- n1.type = types[tptr];
- n1.xoffset = Array_array;
- gmove(&n1, &n3);
}
+ // Load base pointer in n2 = n3.
+ regalloc(&n2, types[tptr], &n3);
+ n3.type = types[tptr];
+ n3.xoffset += Array_array;
+ gmove(&n3, &n2);
+ regfree(&n3);
if (v*w != 0) {
- nodconst(&n2, types[tptr], v*w);
- gins(optoas(OADD, types[tptr]), &n2, &n3);
+ nodconst(&n1, types[tptr], v*w);
+ gins(optoas(OADD, types[tptr]), &n1, &n2);
}
- gmove(&n3, res);
- regfree(&n3);
+ gmove(&n2, res);
+ regfree(&n2);
break;
}
- regalloc(&n2, types[TINT32], &n1); // i
+ // i is in register n1, extend to 32 bits.
+ t = types[TUINT32];
+ if(issigned[n1.type->etype])
+ t = types[TINT32];
+
+ regalloc(&n2, t, &n1); // i
gmove(&n1, &n2);
regfree(&n1);
if(!debug['B'] && !n->bounded) {
// check bounds
if(isconst(nl, CTSTR))
- nodconst(&n1, types[TUINT32], nl->val.u.sval->len);
+ nodconst(&nlen, t, nl->val.u.sval->len);
else if(isslice(nl->type) || nl->type->etype == TSTRING) {
- n1 = n3;
- n1.op = OINDREG;
- n1.type = types[tptr];
- n1.xoffset = Array_nel;
+ nlen = n3;
+ nlen.type = t;
+ nlen.xoffset += Array_nel;
} else
- nodconst(&n1, types[TUINT32], nl->type->bound);
- gins(optoas(OCMP, types[TUINT32]), &n2, &n1);
- p1 = gbranch(optoas(OLT, types[TUINT32]), T, +1);
+ nodconst(&nlen, t, nl->type->bound);
+ gins(optoas(OCMP, t), &n2, &nlen);
+ p1 = gbranch(optoas(OLT, t), T, +1);
if(p2)
patch(p2, pc);
ginscall(panicindex, -1);
patch(p1, pc);
}
-
+
if(isconst(nl, CTSTR)) {
regalloc(&n3, types[tptr], res);
p1 = gins(ALEAL, N, &n3);
@@ -686,24 +701,27 @@ agen(Node *n, Node *res)
goto indexdone;
}
+ // Load base pointer in n3.
+ regalloc(&tmp, types[tptr], &n3);
if(isslice(nl->type) || nl->type->etype == TSTRING) {
- n1 = n3;
- n1.op = OINDREG;
- n1.type = types[tptr];
- n1.xoffset = Array_array;
- gmove(&n1, &n3);
+ n3.type = types[tptr];
+ n3.xoffset += Array_array;
+ gmove(&n3, &tmp);
}
+ regfree(&n3);
+ n3 = tmp;
if(w == 0) {
// nothing to do
} else if(w == 1 || w == 2 || w == 4 || w == 8) {
+ // LEAL (n3)(n2*w), n3
p1 = gins(ALEAL, &n2, &n3);
p1->from.scale = w;
p1->from.index = p1->from.type;
p1->from.type = p1->to.type + D_INDIR;
} else {
- nodconst(&n1, types[TUINT32], w);
- gins(optoas(OMUL, types[TUINT32]), &n1, &n2);
+ nodconst(&tmp, types[TUINT32], w);
+ gins(optoas(OMUL, types[TUINT32]), &tmp, &n2);
gins(optoas(OADD, types[tptr]), &n2, &n3);
}
@@ -861,23 +879,6 @@ igen(Node *n, Node *a, Node *res)
a->type = n->type;
}
-/*
- * generate:
- * newreg = &n;
- *
- * caller must regfree(a).
- */
-void
-agenr(Node *n, Node *a, Node *res)
-{
- Node n1;
-
- tempname(&n1, types[tptr]);
- agen(n, &n1);
- regalloc(a, types[tptr], res);
- gmove(&n1, a);
-}
-
/*
* branch gen
* if(n == true) goto to;
src/cmd/8g/gg.h
--- a/src/cmd/8g/gg.h
+++ b/src/cmd/8g/gg.h
@@ -95,7 +95,6 @@ void ginscall(Node*, int);
* cgen.c
*/
void agen(Node*, Node*);
-void agenr(Node *n, Node *a, Node *res);
void igen(Node*, Node*, Node*);
vlong fieldoffset(Type*, Node*);
void sgen(Node*, Node*, int64);
コアとなるコードの解説
igenindex
関数の変更
- 目的: この関数は、配列やスライスのインデックス
n
を受け取り、そのインデックスに対応するアドレス可能なノードres
を生成します。 - 変更点:
- 関数名が
cgenindex
からigenindex
に変更されました。 if(!is64(n->type))
ブロック内で、n->addable
という新しいチェックが追加されました。n->addable
が真の場合(つまり、インデックスn
が既にメモリ上のアドレスに直接対応している場合)、*res = *n;
として、n
の情報をres
に直接コピーします。これにより、不必要なコード生成(cgen
の呼び出し)を回避し、効率を向上させます。n->addable
が偽の場合、以前と同様に一時的なノードを作成し、cgen(n, res)
を呼び出してコードを生成します。
- 64ビットインデックスを32ビットに変換する際も、
tempname(res, types[TUINT32]);
が追加され、res
が適切に初期化されるようになりました。
- 関数名が
agen
関数の変更
- 目的:
agen
関数は、Goコンパイラにおけるアドレス生成の主要な関数であり、&s[i]
のような操作を処理します。 agenr
の削除:agenr
関数は、アドレスをレジスタに生成するためのヘルパー関数でしたが、このコミットで完全に削除されました。これは、agen
関数自体のロジックが改善され、agenr
の機能が不要になったか、より効率的な方法で統合されたことを意味します。- インデックス処理の改善:
nr->addable
またはnl->addable
の条件分岐内で、インデックスnr
の処理にigenindex
が使用されるようになりました。これにより、インデックスが既にアドレス可能であれば、その情報を直接利用し、冗長なコード生成を避けます。// For fixed array we really want the pointer in n3.
というコメントが追加され、固定長配列の場合にベースポインタをn3
に適切にロードするロジックが追加されました。これは、agen(&n3, &n2)
を呼び出して、配列自体のベースアドレスを取得し、それをn3
に設定するものです。
- ベースポインタのロードの明確化:
- スライスや文字列の要素のアドレスを計算する際、
// Load base pointer in n3.
というコメントが追加され、ベースポインタをn3
にロードする処理がより明確になりました。具体的には、Array_array
フィールド(データ配列の開始アドレス)を介してベースポインタを取得し、それを一時的なノードtmp
に格納し、最終的にn3
に設定します。
- スライスや文字列の要素のアドレスを計算する際、
LEAL
命令の最適化:- 要素の幅
w
が1, 2, 4, 8バイトの場合、gins(ALEAL, &n2, &n3);
という行が追加され、LEAL
命令が生成されるようになりました。 - この
LEAL
命令は、p1->from.scale = w;
のように、scale
フィールドに要素の幅w
を設定することで、LEAL (n3)(n2*w), n3
という形式の命令を生成します。これは、ベースアドレスn3
に、インデックスn2
と要素幅w
を乗算したオフセットを加算し、その結果をn3
に格納するという、非常に効率的なアドレス計算です。 - 以前は、
w
が1, 2, 4, 8バイトの場合でも、OMUL
とOADD
を個別に生成していた可能性がありますが、LEAL
を使用することで、これらの操作を単一の命令で実行できるようになり、命令数の削減と実行効率の向上に貢献します。
- 要素の幅
これらの変更は、コンパイラが&s[i]
のような操作を処理する際に、より少ない中間ステップで、かつより効率的なx86アセンブリ命令(特にLEAL
)を生成するように設計されています。これにより、生成されるバイナリのサイズが小さくなり、実行速度が向上します。
関連リンク
- Go言語の公式ウェブサイト: https://golang.org/
- Go言語のソースコードリポジトリ (GitHub): https://github.com/golang/go
- Go言語のIssueトラッカー (Go Issues): https://github.com/golang/go/issues (コミットメッセージの
Update #1914.
に関連するIssueを検索できます) - Gerrit Change 6574043: https://golang.org/cl/6574043 (このコミットのGerritレビューページ)
参考にした情報源リンク
- x86 Assembly/Instructions/LEAL: https://en.wikibooks.org/wiki/X86_Assembly/Instructions/LEAL
- Go Compiler Internals (非公式な情報源も含む):
- "Go Compiler: The Front End" by Russ Cox (Goのコンパイラに関する公式ブログ記事など): https://go.dev/blog/go1.5-compiler (Go 1.5以降のコンパイラに関する情報ですが、基本的な概念は共通しています)
- "Go Compiler: The Middle End" by Russ Cox: https://go.dev/blog/go1.5-compiler-middle-end
- "Go Compiler: The Back End" by Russ Cox: https://go.dev/blog/go1.5-compiler-back-end
- Go言語のベンチマークに関するドキュメント: https://pkg.go.dev/testing (Goの
testing
パッケージに関する公式ドキュメント) - Go言語のソースコード (特に
src/cmd/8g
ディレクトリ): https://github.com/golang/go/tree/master/src/cmd/8g - Go言語のソースコード (特に
src/cmd/6g
ディレクトリ): https://github.com/golang/go/tree/master/src/cmd/6g - Go言語のソースコード (特に
src/cmd/internal/obj
ディレクトリ): https://github.com/golang/go/tree/master/src/cmd/internal/obj (アセンブリ命令の定義など) - Go言語のソースコード (特に
src/cmd/compile/internal/gc
ディレクトリ): https://github.com/golang/go/tree/master/src/cmd/compile/internal/gc (Go 1.5以降のコンパイラの主要な部分) - Go言語のソースコード (特に
src/cmd/compile/internal/x86
ディレクトリ): https://github.com/golang/go/tree/master/src/cmd/compile/internal/x86 (x86アーキテクチャ固有のコード生成)