[インデックス 15373] ファイルの概要
このコミットは、Go言語のランタイムとコンパイラ(cmd/6g
, cmd/8g
)におけるレジスタの使用方法の変更と、runtime·gogocall
関数の引数追加に関するものです。具体的には、間接呼び出しブロック(indirect call block)に使用するレジスタをAX
からDX
に変更し、runtime·gogocall
にコンテキスト引数r0
を追加しています。
コミット
commit 6066fdcf38bbf92bd551f74a6db4cb72306ed493
Author: Russ Cox <rsc@golang.org>
Date: Fri Feb 22 10:47:54 2013 -0500
cmd/6g, cmd/8g: switch to DX for indirect call block
runtime: add context argument to gogocall
Too many other things use AX, and at least one
(stack zeroing) cannot be moved onto a different
register. Use the less special DX instead.
Preparation for step 2 of http://golang.org/s/go11func.
Nothing interesting here, just split out so that we can
see it's correct before moving on.
R=ken2
CC=golang-dev
https://golang.org/cl/7395050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6066fdcf38bbf92bd551f74a6db4cb72306ed493
元コミット内容
cmd/6g, cmd/8g: switch to DX for indirect call block
runtime: add context argument to gogocall
AX
レジスタは多くの処理で使用されており、特にスタックのゼロクリアなど、他のレジスタに移動できない処理が存在するため、より汎用的なDX
レジスタを代わりに使用するように変更しました。
これは、http://golang.org/s/go11func
のステップ2に向けた準備作業です。このコミット自体は特筆すべき変更を含んでいませんが、次のステップに進む前にこの変更が正しいことを確認するために分割されました。
変更の背景
このコミットの主な背景は、Goランタイムにおけるレジスタの競合問題と、将来的な関数呼び出し規約の変更(http://golang.org/s/go11func
で言及されているもの)への準備です。
x86アーキテクチャ(32ビットの386
と64ビットのamd64
)では、AX
レジスタは関数からの戻り値や、特定の算術演算で暗黙的に使用されるなど、非常に多くの用途で利用されます。そのため、AX
レジスタを間接呼び出しブロックのような重要な処理に固定的に使用すると、他の処理との間でレジスタの競合が発生しやすくなります。特に、スタックのゼロクリアのような低レベルの操作では、特定のレジスタの使用が必須となる場合があり、そのレジスタが他の用途で占有されていると、コードの複雑化やパフォーマンスの低下を招く可能性があります。
このコミットでは、間接呼び出しブロックにAX
ではなくDX
レジスタを使用することで、AX
レジスタの競合を緩和し、より柔軟なレジスタ割り当てを可能にしています。これは、Goランタイムの効率性と堅牢性を向上させるための、低レベルながらも重要な最適化です。
また、コミットメッセージに記載されているhttp://golang.org/s/go11func
は、Go 1.1における関数呼び出し規約の変更に関する提案を指しています。この変更は、Goの関数呼び出しのオーバーヘッドを削減し、特にクロージャやインターフェースメソッドの呼び出し性能を向上させることを目的としていました。このコミットは、その大規模な変更の一部として、レジスタ使用の準備段階として行われたものです。
前提知識の解説
このコミットを理解するためには、以下の技術的な概念について基本的な知識が必要です。
-
レジスタ (Registers): CPU内部にある高速な記憶領域で、CPUが現在処理しているデータや命令のアドレスを一時的に保持します。x86アーキテクチャには汎用レジスタ(
AX
,BX
,CX
,DX
,SI
,DI
,BP
,SP
など)や特殊レジスタ(PC
/IP
、SP
など)があります。AX
(Accumulator Register): 算術演算の主要なレジスタとして使われることが多い。関数からの戻り値も通常ここに格納される。DX
(Data Register):AX
と組み合わせて乗除算に使われたり、汎用データレジスタとして使われたりする。AX
に比べて用途が限定的で、競合が少ない傾向がある。BX
(Base Register): メモリのアドレス計算に使われることが多い。CX
(Count Register): ループカウンタやシフト操作の回数などに使われることが多い。SI
(Source Index Register),DI
(Destination Index Register): 文字列操作やデータ転送のソース/デスティネーションアドレスに使われることが多い。SP
(Stack Pointer Register): スタックの最上位アドレスを指す。PC
/IP
(Program Counter/Instruction Pointer): 次に実行される命令のアドレスを指す。
-
アセンブリ言語 (Assembly Language): CPUが直接理解できる機械語に1対1で対応する低レベルプログラミング言語です。レジスタの操作、メモリへのアクセス、ジャンプ命令などを直接記述します。Goランタイムの低レベル部分は、パフォーマンス最適化のためにアセンブリ言語で記述されることがあります。
MOVL
,MOVQ
,MOVW
: データを移動する命令。L
は32ビット(Long)、Q
は64ビット(Quad)、W
は16ビット(Word)を意味する。JMP
: 無条件ジャンプ。TEXT
: 関数の開始を宣言するディレクティブ。SB
: Static Base。グローバルシンボルや外部シンボルへの参照に使われる。SP
: Stack Pointer。現在のスタックフレームのベースからのオフセットを示す。g(CX)
: Goのg
(goroutine構造体)へのポインタをCX
レジスタから取得する。Goランタイムでは、現在のgoroutineの情報をg
レジスタ(TLS: Thread Local Storage経由でアクセスされる)に保持することが一般的です。
-
Goランタイム (Go Runtime): Goプログラムの実行を管理する部分です。ガベージコレクション、スケジューラ(goroutineの管理)、メモリ割り当て、システムコールなど、Go言語の並行処理やメモリ管理の基盤を提供します。ランタイムの一部は、パフォーマンスが重要なためアセンブリ言語で実装されています。
-
Gobuf
構造体: Goランタイムで使用されるコンテキスト(実行状態)を保存するための構造体です。主にgoroutineの切り替え(コンテキストスイッチ)時に、現在のgoroutineのスタックポインタ(SP
)、プログラムカウンタ(PC
)、現在のg
(goroutine)へのポインタなどを保存・復元するために使用されます。 -
間接呼び出し (Indirect Call): 呼び出す関数のアドレスが、コンパイル時ではなく実行時に決定される呼び出し方法です。関数ポインタやインターフェースメソッドの呼び出しなどで使用されます。
-
go11func
: これは、Go 1.1リリースで導入された関数呼び出し規約の変更に関する提案を指します。この変更は、Goの関数呼び出しのオーバーヘッドを削減し、特にクロージャやインターフェースメソッドの呼び出し性能を向上させることを目的としていました。具体的な内容は、関数呼び出し時のスタックフレームの構造やレジスタの使用方法の見直しを含みます。このコミットは、その大規模な変更の一部として、レジスタ使用の準備段階として行われたものです。
技術的詳細
このコミットは、主に以下の2つの技術的変更を含んでいます。
-
間接呼び出しブロックにおけるレジスタの変更 (
AX
->DX
またはDI
):src/cmd/6g/ggen.c
とsrc/cmd/8g/ggen.c
は、それぞれ32ビット(6g)と64ビット(8g)のGoコンパイラのコード生成部分です。これらのファイルでは、ginscall
関数内で間接呼び出しを行う際に、関数ポインタを格納するレジスタをD_AX
(AX
レジスタ)からD_DX
(DX
レジスタ)に変更しています。 また、src/cmd/8l/pass.c
では、スタックフレームサイズを保存するレジスタがD_DX
からD_DI
に変更されています。これは、DX
レジスタも他の用途で使用される可能性があるため、さらに競合の少ないDI
レジスタに移動したことを示唆しています。この変更の理由は、コミットメッセージにある通り「Too many other things use AX, and at least one (stack zeroing) cannot be moved onto a different register. Use the less special DX instead.」(
AX
レジスタはあまりにも多くの場所で使われており、少なくとも1つ(スタックのゼロクリア)は別のレジスタに移動できない。代わりに、より特殊性の低いDX
を使う。)というものです。これにより、AX
レジスタの競合が緩和され、コンパイラがより効率的にレジスタを割り当てられるようになります。 -
runtime·gogocall
関数へのコンテキスト引数 (uintptr r0
) の追加:src/pkg/runtime/asm_386.s
、src/pkg/runtime/asm_amd64.s
、src/pkg/runtime/asm_arm.s
は、それぞれ32ビットx86、64ビットx86、ARMアーキテクチャ向けのアセンブリコードです。これらのファイルでは、runtime·gogocall
およびruntime·gogocallfn
関数のシグネチャが変更され、新たにuintptr r0
というコンテキスト引数が追加されています。runtime·gogocall(Gobuf*, void (*fn)(void))
からruntime·gogocall(Gobuf*, void (*fn)(void), uintptr r0)
へ- この
r0
引数は、gogocall
が呼び出す関数に渡される追加のコンテキスト情報として機能します。アセンブリコードでは、このr0
がDX
レジスタ(386/amd64)またはR0
レジスタ(ARM)にロードされ、m_cret(BX)
(m
構造体のcret
フィールド)に保存されています。 m_cret
は、m
(machine/thread)構造体の一部であり、おそらく関数呼び出しのコンテキストや戻り値を一時的に保持するためのフィールドです。runtime/runtime.h
の関数プロトタイプもこれに合わせて更新されています。src/pkg/runtime/stack.c
のruntime·newstack
関数では、runtime·gogocall
の呼び出し時にm->cret
が新しい引数として渡されるようになっています。
この変更は、
http://golang.org/s/go11func
で示唆されている関数呼び出し規約の変更と密接に関連しています。新しい呼び出し規約では、関数に渡される引数やコンテキストの管理方法が変更される可能性があり、このr0
引数の追加はそのための準備と考えられます。特に、スタックの拡張や縮小(morestack
/lessstack
)といったランタイムの低レベルな処理において、追加のコンテキスト情報が必要になる場合があります。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードの変更点は以下の通りです。
-
src/cmd/6g/ggen.c
およびsrc/cmd/8g/ggen.c
:ginscall
関数内で、間接呼び出しに使用するレジスタをD_AX
からD_DX
に変更。--- a/src/cmd/6g/ggen.c +++ b/src/cmd/6g/ggen.c @@ -77,7 +77,7 @@ ginscall(Node *f, int proc)\ tgins(AUNDEF, N, N);\ break;\ }\ - tnodreg(®, types[tptr], D_AX);\ + tnodreg(®, types[tptr], D_DX);\ tnodreg(&r1, types[tptr], D_BX);\ tgmove(f, ®);\ reg.op = OINDREG;
-
src/cmd/8l/pass.c
:- スタックフレームサイズを保存するレジスタを
D_DX
からD_DI
に変更。--- a/src/cmd/8l/pass.c +++ b/src/cmd/8l/pass.c @@ -539,9 +539,9 @@ dostkoff(void)\ tq = p;\ }\ - tp = appendp(p); // save frame size in DX + tp = appendp(p); // save frame size in DI tp->as = AMOVL;\ - tp->to.type = D_DX;\ + tp->to.type = D_DI;\ tp->from.type = D_CONST; // If we ask for more stack, we'll get a minimum of StackMin bytes.
- スタックフレームサイズを保存するレジスタを
-
src/pkg/runtime/asm_386.s
(32-bit x86 アセンブリ):runtime·gogocall
およびruntime·gogocallfn
のシグネチャ変更と、DX
レジスタへのコンテキスト引数r0
のロード、gobuf_g
のロード先レジスタをDX
からDI
へ変更、g(CX)
へのストア元レジスタをDX
からDI
へ変更。runtime·morestack
でm_cret(BX)
にDX
を保存する行を追加。runtime·jmpdefer
でAX
からDX
へのレジスタ変更。--- a/src/pkg/runtime/asm_386.s +++ b/src/pkg/runtime/asm_386.s @@ -134,16 +134,17 @@ TEXT runtime·gogo(SB), 7, $0 MOVL gobuf_pc(BX), BX JMP BX -// void gogocall(Gobuf*, void (*fn)(void)) +// void gogocall(Gobuf*, void (*fn)(void), uintptr r0) // restore state from Gobuf but then call fn. // (call fn, returning to state in Gobuf) TEXT runtime·gogocall(SB), 7, $0 + MOVL 12(SP), DX // context MOVL 8(SP), AX // fn MOVL 4(SP), BX // gobuf - MOVL gobuf_g(BX), DX + MOVL gobuf_g(BX), DI get_tls(CX) - MOVL DX, g(CX) - MOVL 0(DX), CX // make sure g != nil + MOVL DI, g(CX) + MOVL 0(DI), CX // make sure g != nil MOVL gobuf_sp(BX), SP // restore SP MOVL gobuf_pc(BX), BX PUSHL BX @@ -154,16 +155,16 @@ TEXT runtime·gogocall(SB), 7, $0 // restore state from Gobuf but then call fn. // (call fn, returning to state in Gobuf) TEXT runtime·gogocallfn(SB), 7, $0 - MOVL 8(SP), AX // fn + MOVL 8(SP), DX // fn MOVL 4(SP), BX // gobuf - MOVL gobuf_g(BX), DX + MOVL gobuf_g(BX), DI get_tls(CX) - MOVL DX, g(CX) - MOVL 0(DX), CX // make sure g != nil + MOVL DI, g(CX) + MOVL 0(DI), CX // make sure g != nil MOVL gobuf_sp(BX), SP // restore SP MOVL gobuf_pc(BX), BX PUSHL BX - MOVL 0(AX), BX + MOVL 0(DX), BX JMP BX POPL BX // not reached @@ -209,11 +210,13 @@ TEXT runtime·morestack(SB),7,$0 CMPL g(CX), SI JNE 2(PC) INT $3 + + MOVL DX, m_cret(BX) - // frame size in DX + // frame size in DI // arg size in AX // Save in m. - MOVL DX, m_moreframesize(BX) + MOVL DI, m_moreframesize(BX) MOVL AX, m_moreargsize(BX) // Called from f. @@ -441,11 +444,11 @@ TEXT runtime·atomicstore64(SB), 7, $0 // 2. sub 5 bytes from the callers return // 3. jmp to the argument TEXT runtime·jmpdefer(SB), 7, $0 - MOVL 4(SP), AX // fn + MOVL 4(SP), DX // fn MOVL 8(SP), BX // caller sp LEAL -4(BX), SP // caller sp after CALL SUBL $5, (SP) // return to CALL again - MOVL 0(AX), BX + MOVL 0(DX), BX JMP BX // but first run the deferred function // Dummy function to use in saved gobuf.PC,
-
src/pkg/runtime/asm_amd64.s
(64-bit x86 アセンブリ):runtime·gogocall
およびruntime·gogocallfn
のシグネチャ変更と、DX
レジスタへのコンテキスト引数r0
のロード、gobuf_g
のロード先レジスタをDX
からDI
へ変更、g(CX)
へのストア元レジスタをDX
からAX
へ変更。runtime·morestack
でm_cret(BX)
にDX
を保存する行を追加。runtime·jmpdefer
でAX
からDX
へのレジスタ変更。--- a/src/pkg/runtime/asm_amd64.s +++ b/src/pkg/runtime/asm_amd64.s @@ -121,16 +121,17 @@ TEXT runtime·gogo(SB), 7, $0 MOVQ gobuf_pc(BX), BX JMP BX -// void gogocall(Gobuf*, void (*fn)(void)) +// void gogocall(Gobuf*, void (*fn)(void), uintptr r0) // restore state from Gobuf but then call fn. // (call fn, returning to state in Gobuf) TEXT runtime·gogocall(SB), 7, $0 + MOVQ 24(SP), DX // context MOVQ 16(SP), AX // fn MOVQ 8(SP), BX // gobuf - MOVQ gobuf_g(BX), DX + MOVQ gobuf_g(BX), DI get_tls(CX) - MOVQ DX, g(CX) - MOVQ 0(DX), CX // make sure g != nil + MOVQ DI, g(CX) + MOVQ 0(DI), CX // make sure g != nil MOVQ gobuf_sp(BX), SP // restore SP MOVQ gobuf_pc(BX), BX PUSHQ BX @@ -141,16 +142,16 @@ TEXT runtime·gogocall(SB), 7, $0 // restore state from Gobuf but then call fn. // (call fn, returning to state in Gobuf) TEXT runtime·gogocallfn(SB), 7, $0 - MOVQ 16(SP), AX // fn + MOVQ 16(SP), DX // fn MOVQ 8(SP), BX // gobuf - MOVQ gobuf_g(BX), DX + MOVQ gobuf_g(BX), AX get_tls(CX) - MOVQ DX, g(CX) - MOVQ 0(DX), CX // make sure g != nil + MOVQ AX, g(CX) + MOVQ 0(AX), CX // make sure g != nil MOVQ gobuf_sp(BX), SP // restore SP MOVQ gobuf_pc(BX), BX PUSHQ BX - MOVQ 0(AX), BX + MOVQ 0(DX), BX JMP BX POPQ BX // not reached @@ -195,6 +196,8 @@ TEXT runtime·morestack(SB),7,$0 CMPQ g(CX), SI JNE 2(PC) INT $3 + + MOVQ DX, m_cret(BX) // Called from f. // Set m->morebuf to f's caller. @@ -471,11 +474,11 @@ TEXT runtime·atomicstore64(SB), 7, $0 // 2. sub 5 bytes from the callers return // 3. jmp to the argument TEXT runtime·jmpdefer(SB), 7, $0 - MOVQ 8(SP), AX // fn + MOVQ 8(SP), DX // fn MOVQ 16(SP), BX // caller sp LEAQ -8(BX), SP // caller sp after CALL SUBQ $5, (SP) // return to CALL again - MOVQ 0(AX), BX + MOVQ 0(DX), BX JMP BX // but first run the deferred function // Dummy function to use in saved gobuf.PC,
-
src/pkg/runtime/asm_arm.s
(ARM アセンブリ):runtime·gogocall
のシグネチャ変更と、R0
レジスタへのコンテキスト引数r0
のロード。runtime·morestack
でm_cret(m)
にR0
を保存する行を追加。--- a/src/pkg/runtime/asm_arm.s +++ b/src/pkg/runtime/asm_arm.s @@ -112,19 +112,19 @@ TEXT runtime·gogo(SB), 7, $-4 MOVW gobuf_sp(R1), SP // restore SP MOVW gobuf_pc(R1), PC -// void gogocall(Gobuf*, void (*fn)(void)) +// void gogocall(Gobuf*, void (*fn)(void), uintptr r0) // restore state from Gobuf but then call fn. // (call fn, returning to state in Gobuf) // using frame size $-4 means do not save LR on stack. TEXT runtime·gogocall(SB), 7, $-4 MOVW 0(FP), R3 // gobuf MOVW 4(FP), R1 // fn - MOVW 8(FP), R2 // fp offset MOVW gobuf_g(R3), g MOVW 0(g), R0 // make sure g != nil MOVW cgo_save_gm(SB), R0 CMP $0, R0 // if in Cgo, we have to save g and m BL.NE (R0) // this call will clobber R0 + MOVW 8(FP), R0 // context MOVW gobuf_sp(R3), SP // restore SP MOVW gobuf_pc(R3), LR MOVW R1, PC @@ -136,7 +136,6 @@ TEXT runtime·gogocall(SB), 7, $-4 TEXT runtime·gogocallfn(SB), 7, $-4 MOVW 0(FP), R3 // gobuf MOVW 4(FP), R1 // fn - MOVW 8(FP), R2 // fp offset MOVW gobuf_g(R3), g MOVW 0(g), R0 // make sure g != nil MOVW cgo_save_gm(SB), R0 @@ -189,6 +188,7 @@ TEXT runtime·morestack(SB),7,$-4 BL.EQ runtime·abort(SB) // Save in m. + MOVW R0, m_cret(m) // function context MOVW R1, m_moreframesize(m) MOVW R2, m_moreargsize(m)
-
src/pkg/runtime/runtime.h
:runtime·gogocall
関数のプロトタイプ宣言を更新。--- a/src/pkg/runtime/runtime.h +++ b/src/pkg/runtime/runtime.h @@ -615,7 +615,7 @@ int32 runtime·charntorune(int32*, uint8*, int32); #define FLUSH(x) USED(x) void runtime·gogo(Gobuf*, uintptr); -void runtime·gogocall(Gobuf*, void(*)(void)); +void runtime·gogocall(Gobuf*, void(*)(void), uintptr); void runtime·gogocallfn(Gobuf*, FuncVal*); void runtime·gosave(Gobuf*); void runtime·lessstack(void);
-
src/pkg/runtime/stack.c
:runtime·newstack
関数内でruntime·gogocall
を呼び出す際に、m->cret
を新しい引数として渡すように変更。--- a/src/pkg/runtime/stack.c +++ b/src/pkg/runtime/stack.c @@ -276,7 +276,7 @@ runtime·newstack(void)\ if(reflectcall)\ runtime·gogocallfn(&label, (FuncVal*)m->morepc);\ else\ - runtime·gogocall(&label, m->morepc);\ + runtime·gogocall(&label, m->morepc, m->cret);\ *(int32*)345 = 123; // never return }
コアとなるコードの解説
このコミットの核となる変更は、Goランタイムが関数呼び出しのコンテキストをどのように管理し、レジスタをどのように利用するかという点にあります。
-
レジスタの再割り当て: コンパイラ(
6g
,8g
)が生成するコードにおいて、間接呼び出しのアドレスを保持するレジスタがAX
からDX
(またはDI
)に変更されました。これは、AX
レジスタがGoランタイムの他の重要な部分(特にスタックのゼロクリア)で不可欠であり、その使用を減らすことでレジスタの競合を避け、より効率的なコード生成を可能にするためです。低レベルのアセンブリコードでは、レジスタの選択はパフォーマンスに直接影響するため、このような変更は重要です。 -
runtime·gogocall
へのコンテキスト引数追加:runtime·gogocall
は、Goランタイムがgoroutineのコンテキストを切り替える際に使用する低レベルの関数です。この関数にuintptr r0
という新しい引数が追加されたことは、Goランタイムが関数呼び出しの際に、より多くのコンテキスト情報を渡す必要があることを示唆しています。gogocall
は、Gobuf
構造体から保存された状態(スタックポインタ、プログラムカウンタなど)を復元し、指定された関数fn
を呼び出します。- 追加された
r0
引数は、アセンブリコード内でDX
(x86)またはR0
(ARM)レジスタにロードされ、最終的にm
構造体のcret
フィールドに保存されます。 m
構造体は、GoランタイムにおけるOSスレッド(M: Machine)の情報を保持しており、cret
フィールドは「call return context」のような意味合いを持つと考えられます。これは、関数呼び出しの戻り値や、呼び出し元のコンテキストに関する追加情報がここに格納されることを示唆しています。runtime·newstack
関数(スタックの拡張が必要な場合に呼び出される)がgogocall
を呼び出す際に、m->cret
を渡すようになったことから、スタックの管理や関数呼び出しの際に、このcret
が重要な役割を果たすことがわかります。
これらの変更は、Go 1.1で導入された新しい関数呼び出し規約(go11func
)の準備段階として行われました。この規約変更は、Goの関数呼び出しのオーバーヘッドを削減し、特にクロージャやインターフェースメソッドの呼び出し性能を向上させることを目的としていました。レジスタの効率的な利用と、関数呼び出しコンテキストのより柔軟な管理は、これらの目標達成に不可欠な要素です。
関連リンク
- Go 1.1 Release Notes: Go 1.1のリリースノートには、このコミットが関連する関数呼び出し規約の変更やパフォーマンス改善に関する情報が含まれている可能性があります。
- Go Assembly Language: Goのアセンブリ言語に関する公式ドキュメントやチュートリアル。
- Go Runtime Source Code: Goランタイムのソースコードは、これらの低レベルな変更の全体像を理解する上で不可欠です。
参考にした情報源リンク
http://golang.org/s/go11func
: このURLは、Go 1.1における関数呼び出し規約の変更に関する提案ドキュメントを指しています。このコミットの背景を深く理解するために最も重要な情報源です。- https://go.dev/s/go11func (リダイレクト後の正しいURL)
- x86 Instruction Set Reference: x86アーキテクチャのレジスタと命令に関する詳細な情報。
- Intel 64 and IA-32 Architectures Software Developer's Manuals (Intelの公式ドキュメント)
- ARM Architecture Reference Manual: ARMアーキテクチャのレジスタと命令に関する詳細な情報。
- ARM Architecture Reference Manual (ARMの公式ドキュメント)
- Goのソースコード: 特に
src/cmd/
,src/pkg/runtime/
以下のファイル。 - GoのIssue Tracker: 関連するバグ報告や機能リクエスト。
- GoのCode Reviewシステム (Gerrit): コミットメッセージに記載されている
https://golang.org/cl/7395050
は、GoのGerritコードレビューシステムへのリンクです。ここには、この変更に関する議論や追加のコンテキストが含まれている可能性があります。- https://go.dev/cl/7395050 (リダイレクト後の正しいURL)
- Goのメーリングリスト (golang-dev): 開発者間の議論。
[インデックス 15373] ファイルの概要
このコミットは、Go言語のランタイムとコンパイラ(cmd/6g
, cmd/8g
)におけるレジスタの使用方法の変更と、runtime·gogocall
関数の引数追加に関するものです。具体的には、間接呼び出しブロック(indirect call block)に使用するレジスタをAX
からDX
に変更し、runtime·gogocall
にコンテキスト引数r0
を追加しています。
コミット
commit 6066fdcf38bbf92bd551f74a6db4cb72306ed493
Author: Russ Cox <rsc@golang.org>
Date: Fri Feb 22 10:47:54 2013 -0500
cmd/6g, cmd/8g: switch to DX for indirect call block
runtime: add context argument to gogocall
Too many other things use AX, and at least one
(stack zeroing) cannot be moved onto a different
register. Use the less special DX instead.
Preparation for step 2 of http://golang.org/s/go11func.
Nothing interesting here, just split out so that we can
see it's correct before moving on.
R=ken2
CC=golang-dev
https://golang.org/cl/7395050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6066fdcf38bbf92bd551f74a6db4cb72306ed493
元コミット内容
cmd/6g, cmd/8g: switch to DX for indirect call block
runtime: add context argument to gogocall
AX
レジスタは多くの処理で使用されており、特にスタックのゼロクリアなど、他のレジスタに移動できない処理が存在するため、より汎用的なDX
レジスタを代わりに使用するように変更しました。
これは、http://golang.org/s/go11func
のステップ2に向けた準備作業です。このコミット自体は特筆すべき変更を含んでいませんが、次のステップに進む前にこの変更が正しいことを確認するために分割されました。
変更の背景
このコミットの主な背景は、Goランタイムにおけるレジスタの競合問題と、将来的な関数呼び出し規約の変更(http://golang.org/s/go11func
で言及されているもの)への準備です。
x86アーキテクチャ(32ビットの386
と64ビットのamd64
)では、AX
レジスタは関数からの戻り値や、特定の算術演算で暗黙的に使用されるなど、非常に多くの用途で利用されます。そのため、AX
レジスタを間接呼び出しブロックのような重要な処理に固定的に使用すると、他の処理との間でレジスタの競合が発生しやすくなります。特に、スタックのゼロクリアのような低レベルの操作では、特定のレジスタの使用が必須となる場合があり、そのレジスタが他の用途で占有されていると、コードの複雑化やパフォーマンスの低下を招く可能性があります。
このコミットでは、間接呼び出しブロックにAX
ではなくDX
レジスタを使用することで、AX
レジスタの競合を緩和し、より柔軟なレジスタ割り当てを可能にしています。これは、Goランタイムの効率性と堅牢性を向上させるための、低レベルながらも重要な最適化です。
また、コミットメッセージに記載されているhttp://golang.org/s/go11func
は、Go 1.1における関数呼び出し規約の変更に関する提案を指しています。この変更は、Goの関数呼び出しのオーバーヘッドを削減し、特にクロージャやインターフェースメソッドの呼び出し性能を向上させることを目的としていました。このコミットは、その大規模な変更の一部として、レジスタ使用の準備段階として行われたものです。
前提知識の解説
このコミットを理解するためには、以下の技術的な概念について基本的な知識が必要です。
-
レジスタ (Registers): CPU内部にある高速な記憶領域で、CPUが現在処理しているデータや命令のアドレスを一時的に保持します。x86アーキテクチャには汎用レジスタ(
AX
,BX
,CX
,DX
,SI
,DI
,BP
,SP
など)や特殊レジスタ(PC
/IP
、SP
など)があります。AX
(Accumulator Register): 算術演算の主要なレジスタとして使われることが多い。関数からの戻り値も通常ここに格納される。DX
(Data Register):AX
と組み合わせて乗除算に使われたり、汎用データレジスタとして使われたりする。AX
に比べて用途が限定的で、競合が少ない傾向がある。BX
(Base Register): メモリのアドレス計算に使われることが多い。CX
(Count Register): ループカウンタやシフト操作の回数などに使われることが多い。SI
(Source Index Register),DI
(Destination Index Register): 文字列操作やデータ転送のソース/デスティネーションアドレスに使われることが多い。SP
(Stack Pointer Register): スタックの最上位アドレスを指す。PC
/IP
(Program Counter/Instruction Pointer): 次に実行される命令のアドレスを指す。
-
アセンブリ言語 (Assembly Language): CPUが直接理解できる機械語に1対1で対応する低レベルプログラミング言語です。レジスタの操作、メモリへのアクセス、ジャンプ命令などを直接記述します。Goランタイムの低レベル部分は、パフォーマンス最適化のためにアセンブリ言語で記述されることがあります。
MOVL
,MOVQ
,MOVW
: データを移動する命令。L
は32ビット(Long)、Q
は64ビット(Quad)、W
は16ビット(Word)を意味する。JMP
: 無条件ジャンプ。TEXT
: 関数の開始を宣言するディレクティブ。SB
: Static Base。グローバルシンボルや外部シンボルへの参照に使われる。SP
: Stack Pointer。現在のスタックフレームのベースからのオフセットを示す。g(CX)
: Goのg
(goroutine構造体)へのポインタをCX
レジスタから取得する。Goランタイムでは、現在のgoroutineの情報をg
レジスタ(TLS: Thread Local Storage経由でアクセスされる)に保持することが一般的です。
-
Goランタイム (Go Runtime): Goプログラムの実行を管理する部分です。ガベージコレクション、スケジューラ(goroutineの管理)、メモリ割り当て、システムコールなど、Go言語の並行処理やメモリ管理の基盤を提供します。ランタイムの一部は、パフォーマンスが重要なためアセンブリ言語で実装されています。
-
Gobuf
構造体: Goランタイムで使用されるコンテキスト(実行状態)を保存するための構造体です。主にgoroutineの切り替え(コンテキストスイッチ)時に、現在のgoroutineのスタックポインタ(SP
)、プログラムカウンタ(PC
)、現在のg
(goroutine)へのポインタなどを保存・復元するために使用されます。 -
間接呼び出し (Indirect Call): 呼び出す関数のアドレスが、コンパイル時ではなく実行時に決定される呼び出し方法です。関数ポインタやインターフェースメソッドの呼び出しなどで使用されます。
-
go11func
: これは、Go 1.1リリースで導入された関数呼び出し規約の変更に関する提案を指します。この変更は、Goの関数呼び出しのオーバーヘッドを削減し、特にクロージャやインターフェースメソッドの呼び出し性能を向上させることを目的としていました。具体的な内容は、関数呼び出し時のスタックフレームの構造やレジスタの使用方法の見直しを含みます。このコミットは、その大規模な変更の一部として、レジスタ使用の準備段階として行われたものです。
技術的詳細
このコミットは、主に以下の2つの技術的変更を含んでいます。
-
間接呼び出しブロックにおけるレジスタの変更 (
AX
->DX
またはDI
):src/cmd/6g/ggen.c
とsrc/cmd/8g/ggen.c
は、それぞれ32ビット(6g)と64ビット(8g)のGoコンパイラのコード生成部分です。これらのファイルでは、ginscall
関数内で間接呼び出しを行う際に、関数ポインタを格納するレジスタをD_AX
(AX
レジスタ)からD_DX
(DX
レジスタ)に変更しています。 また、src/cmd/8l/pass.c
では、スタックフレームサイズを保存するレジスタがD_DX
からD_DI
に変更されています。これは、DX
レジスタも他の用途で使用される可能性があるため、さらに競合の少ないDI
レジスタに移動したことを示唆しています。この変更の理由は、コミットメッセージにある通り「Too many other things use AX, and at least one (stack zeroing) cannot be moved onto a different register. Use the less special DX instead.」(
AX
レジスタはあまりにも多くの場所で使われており、少なくとも1つ(スタックのゼロクリア)は別のレジスタに移動できない。代わりに、より特殊性の低いDX
を使う。)というものです。これにより、AX
レジスタの競合が緩和され、コンパイラがより効率的にレジスタを割り当てられるようになります。 -
runtime·gogocall
関数へのコンテキスト引数 (uintptr r0
) の追加:src/pkg/runtime/asm_386.s
、src/pkg/runtime/asm_amd64.s
、src/pkg/runtime/asm_arm.s
は、それぞれ32ビットx86、64ビットx86、ARMアーキテクチャ向けのアセンブリコードです。これらのファイルでは、runtime·gogocall
およびruntime·gogocallfn
関数のシグネチャが変更され、新たにuintptr r0
というコンテキスト引数が追加されています。runtime·gogocall(Gobuf*, void (*fn)(void))
からruntime·gogocall(Gobuf*, void (*fn)(void), uintptr r0)
へ- この
r0
引数は、gogocall
が呼び出す関数に渡される追加のコンテキスト情報として機能します。アセンブリコードでは、このr0
がDX
レジスタ(386/amd64)またはR0
レジスタ(ARM)にロードされ、m_cret(BX)
(m
構造体のcret
フィールド)に保存されています。 m_cret
は、m
(machine/thread)構造体の一部であり、おそらく関数呼び出しのコンテキストや戻り値を一時的に保持するためのフィールドです。runtime/runtime.h
の関数プロトタイプもこれに合わせて更新されています。src/pkg/runtime/stack.c
のruntime·newstack
関数では、runtime·gogocall
の呼び出し時にm->cret
が新しい引数として渡されるようになっています。
この変更は、
http://golang.org/s/go11func
で示唆されている関数呼び出し規約の変更と密接に関連しています。新しい呼び出し規約では、関数に渡される引数やコンテキストの管理方法が変更される可能性があり、このr0
引数の追加はそのための準備と考えられます。特に、スタックの拡張や縮小(morestack
/lessstack
)といったランタイムの低レベルな処理において、追加のコンテキスト情報が必要になる場合があります。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードの変更点は以下の通りです。
-
src/cmd/6g/ggen.c
およびsrc/cmd/8g/ggen.c
:ginscall
関数内で、間接呼び出しに使用するレジスタをD_AX
からD_DX
に変更。--- a/src/cmd/6g/ggen.c +++ b/src/cmd/6g/ggen.c @@ -77,7 +77,7 @@ ginscall(Node *f, int proc)\ tgins(AUNDEF, N, N);\ break;\ }\ - tnodreg(®, types[tptr], D_AX);\ + tnodreg(®, types[tptr], D_DX);\ tnodreg(&r1, types[tptr], D_BX);\ tgmove(f, ®);\ reg.op = OINDREG;
-
src/cmd/8l/pass.c
:- スタックフレームサイズを保存するレジスタを
D_DX
からD_DI
に変更。--- a/src/cmd/8l/pass.c +++ b/src/cmd/8l/pass.c @@ -539,9 +539,9 @@ dostkoff(void)\ tq = p;\ }\ - tp = appendp(p); // save frame size in DX + tp = appendp(p); // save frame size in DI tp->as = AMOVL;\ - tp->to.type = D_DX;\ + tp->to.type = D_DI;\ tp->from.type = D_CONST; // If we ask for more stack, we'll get a minimum of StackMin bytes.
- スタックフレームサイズを保存するレジスタを
-
src/pkg/runtime/asm_386.s
(32-bit x86 アセンブリ):runtime·gogocall
およびruntime·gogocallfn
のシグネチャ変更と、DX
レジスタへのコンテキスト引数r0
のロード、gobuf_g
のロード先レジスタをDX
からDI
へ変更、g(CX)
へのストア元レジスタをDX
からDI
へ変更。runtime·morestack
でm_cret(BX)
にDX
を保存する行を追加。runtime·jmpdefer
でAX
からDX
へのレジスタ変更。--- a/src/pkg/runtime/asm_386.s +++ b/src/pkg/runtime/asm_386.s @@ -134,16 +134,17 @@ TEXT runtime·gogo(SB), 7, $0 MOVL gobuf_pc(BX), BX JMP BX -// void gogocall(Gobuf*, void (*fn)(void)) +// void gogocall(Gobuf*, void (*fn)(void), uintptr r0) // restore state from Gobuf but then call fn. // (call fn, returning to state in Gobuf) TEXT runtime·gogocall(SB), 7, $0 + MOVL 12(SP), DX // context MOVL 8(SP), AX // fn MOVL 4(SP), BX // gobuf - MOVL gobuf_g(BX), DX + MOVL gobuf_g(BX), DI get_tls(CX) - MOVL DX, g(CX) - MOVL 0(DX), CX // make sure g != nil + MOVL DI, g(CX) + MOVL 0(DI), CX // make sure g != nil MOVL gobuf_sp(BX), SP // restore SP MOVL gobuf_pc(BX), BX PUSHL BX @@ -154,16 +155,16 @@ TEXT runtime·gogocall(SB), 7, $0 // restore state from Gobuf but then call fn. // (call fn, returning to state in Gobuf) TEXT runtime·gogocallfn(SB), 7, $0 - MOVL 8(SP), AX // fn + MOVL 8(SP), DX // fn MOVL 4(SP), BX // gobuf - MOVL gobuf_g(BX), DX + MOVL gobuf_g(BX), DI get_tls(CX) - MOVL DX, g(CX) - MOVL 0(DX), CX // make sure g != nil + MOVL DI, g(CX) + MOVL 0(DI), CX // make sure g != nil MOVL gobuf_sp(BX), SP // restore SP MOVL gobuf_pc(BX), BX PUSHL BX - MOVL 0(AX), BX + MOVL 0(DX), BX JMP BX POPL BX // not reached @@ -209,11 +210,13 @@ TEXT runtime·morestack(SB),7,$0 CMPL g(CX), SI JNE 2(PC) INT $3 + + MOVL DX, m_cret(BX) - // frame size in DX + // frame size in DI // arg size in AX // Save in m. - MOVL DX, m_moreframesize(BX) + MOVL DI, m_moreframesize(BX) MOVL AX, m_moreargsize(BX) // Called from f. @@ -441,11 +444,11 @@ TEXT runtime·atomicstore64(SB), 7, $0 // 2. sub 5 bytes from the callers return // 3. jmp to the argument TEXT runtime·jmpdefer(SB), 7, $0 - MOVL 4(SP), AX // fn + MOVL 4(SP), DX // fn MOVL 8(SP), BX // caller sp LEAL -4(BX), SP // caller sp after CALL SUBL $5, (SP) // return to CALL again - MOVL 0(AX), BX + MOVL 0(DX), BX JMP BX // but first run the deferred function // Dummy function to use in saved gobuf.PC,
-
src/pkg/runtime/asm_amd64.s
(64-bit x86 アセンブリ):runtime·gogocall
およびruntime·gogocallfn
のシグネチャ変更と、DX
レジスタへのコンテキスト引数r0
のロード、gobuf_g
のロード先レジスタをDX
からDI
へ変更、g(CX)
へのストア元レジスタをDX
からAX
へ変更。runtime·morestack
でm_cret(BX)
にDX
を保存する行を追加。runtime·jmpdefer
でAX
からDX
へのレジスタ変更。--- a/src/pkg/runtime/asm_amd64.s +++ b/src/pkg/runtime/asm_amd64.s @@ -121,16 +121,17 @@ TEXT runtime·gogo(SB), 7, $0 MOVQ gobuf_pc(BX), BX JMP BX -// void gogocall(Gobuf*, void (*fn)(void)) +// void gogocall(Gobuf*, void (*fn)(void), uintptr r0) // restore state from Gobuf but then call fn. // (call fn, returning to state in Gobuf) TEXT runtime·gogocall(SB), 7, $0 + MOVQ 24(SP), DX // context MOVQ 16(SP), AX // fn MOVQ 8(SP), BX // gobuf - MOVQ gobuf_g(BX), DX + MOVQ gobuf_g(BX), DI get_tls(CX) - MOVQ DX, g(CX) - MOVQ 0(DX), CX // make sure g != nil + MOVQ DI, g(CX) + MOVQ 0(DI), CX // make sure g != nil MOVQ gobuf_sp(BX), SP // restore SP MOVQ gobuf_pc(BX), BX PUSHQ BX @@ -141,16 +142,16 @@ TEXT runtime·gogocall(SB), 7, $0 // restore state from Gobuf but then call fn. // (call fn, returning to state in Gobuf) TEXT runtime·gogocallfn(SB), 7, $0 - MOVQ 16(SP), AX // fn + MOVQ 16(SP), DX // fn MOVQ 8(SP), BX // gobuf - MOVQ gobuf_g(BX), DX + MOVQ gobuf_g(BX), AX get_tls(CX) - MOVQ DX, g(CX) - MOVQ 0(DX), CX // make sure g != nil + MOVQ AX, g(CX) + MOVQ 0(AX), CX // make sure g != nil MOVQ gobuf_sp(BX), SP // restore SP MOVQ gobuf_pc(BX), BX PUSHQ BX - MOVQ 0(AX), BX + MOVQ 0(DX), BX JMP BX POPQ BX // not reached @@ -195,6 +196,8 @@ TEXT runtime·morestack(SB),7,$0 CMPQ g(CX), SI JNE 2(PC) INT $3 + + MOVQ DX, m_cret(BX) // Called from f. // Set m->morebuf to f's caller. @@ -471,11 +474,11 @@ TEXT runtime·atomicstore64(SB), 7, $0 // 2. sub 5 bytes from the callers return // 3. jmp to the argument TEXT runtime·jmpdefer(SB), 7, $0 - MOVQ 8(SP), AX // fn + MOVQ 8(SP), DX // fn MOVQ 16(SP), BX // caller sp LEAQ -8(BX), SP // caller sp after CALL SUBQ $5, (SP) // return to CALL again - MOVQ 0(AX), BX + MOVQ 0(DX), BX JMP BX // but first run the deferred function // Dummy function to use in saved gobuf.PC,
-
src/pkg/runtime/asm_arm.s
(ARM アセンブリ):runtime·gogocall
のシグネチャ変更と、R0
レジスタへのコンテキスト引数r0
のロード。runtime·morestack
でm_cret(m)
にR0
を保存する行を追加。--- a/src/pkg/runtime/asm_arm.s +++ b/src/pkg/runtime/asm_arm.s @@ -112,19 +112,19 @@ TEXT runtime·gogo(SB), 7, $-4 MOVW gobuf_sp(R1), SP // restore SP MOVW gobuf_pc(R1), PC -// void gogocall(Gobuf*, void (*fn)(void)) +// void gogocall(Gobuf*, void (*fn)(void), uintptr r0) // restore state from Gobuf but then call fn. // (call fn, returning to state in Gobuf) // using frame size $-4 means do not save LR on stack. TEXT runtime·gogocall(SB), 7, $-4 MOVW 0(FP), R3 // gobuf MOVW 4(FP), R1 // fn - MOVW 8(FP), R2 // fp offset MOVW gobuf_g(R3), g MOVW 0(g), R0 // make sure g != nil MOVW cgo_save_gm(SB), R0 CMP $0, R0 // if in Cgo, we have to save g and m BL.NE (R0) // this call will clobber R0 + MOVW 8(FP), R0 // context MOVW gobuf_sp(R3), SP // restore SP MOVW gobuf_pc(R3), LR MOVW R1, PC @@ -136,7 +136,6 @@ TEXT runtime·gogocall(SB), 7, $-4 TEXT runtime·gogocallfn(SB), 7, $-4 MOVW 0(FP), R3 // gobuf MOVW 4(FP), R1 // fn - MOVW 8(FP), R2 // fp offset MOVW gobuf_g(R3), g MOVW 0(g), R0 // make sure g != nil MOVW cgo_save_gm(SB), R0 @@ -189,6 +188,7 @@ TEXT runtime·morestack(SB),7,$-4 BL.EQ runtime·abort(SB) // Save in m. + MOVW R0, m_cret(m) // function context MOVW R1, m_moreframesize(m) MOVW R2, m_moreargsize(m)
-
src/pkg/runtime/runtime.h
:runtime·gogocall
関数のプロトタイプ宣言を更新。--- a/src/pkg/runtime/runtime.h +++ b/src/pkg/runtime/runtime.h @@ -615,7 +615,7 @@ int32 runtime·charntorune(int32*, uint8*, int32); #define FLUSH(x) USED(x) void runtime·gogo(Gobuf*, uintptr); -void runtime·gogocall(Gobuf*, void(*)(void)); +void runtime·gogocall(Gobuf*, void(*)(void), uintptr); void runtime·gogocallfn(Gobuf*, FuncVal*); void runtime·gosave(Gobuf*); void runtime·lessstack(void);
-
src/pkg/runtime/stack.c
:runtime·newstack
関数内でruntime·gogocall
を呼び出す際に、m->cret
を新しい引数として渡すように変更。--- a/src/pkg/runtime/stack.c +++ b/src/pkg/runtime/stack.c @@ -276,7 +276,7 @@ runtime·newstack(void)\ if(reflectcall)\ runtime·gogocallfn(&label, (FuncVal*)m->morepc);\ else\ - runtime·gogocall(&label, m->morepc);\ + runtime·gogocall(&label, m->morepc, m->cret);\ *(int32*)345 = 123;\t// never return }
コアとなるコードの解説
このコミットの核となる変更は、Goランタイムが関数呼び出しのコンテキストをどのように管理し、レジスタをどのように利用するかという点にあります。
-
レジスタの再割り当て: コンパイラ(
6g
,8g
)が生成するコードにおいて、間接呼び出しのアドレスを保持するレジスタがAX
からDX
(またはDI
)に変更されました。これは、AX
レジスタがGoランタイムの他の重要な部分(特にスタックのゼロクリア)で不可欠であり、その使用を減らすことでレジスタの競合を避け、より効率的なコード生成を可能にするためです。低レベルのアセンブリコードでは、レジスタの選択はパフォーマンスに直接影響するため、このような変更は重要です。 -
runtime·gogocall
へのコンテキスト引数追加:runtime·gogocall
は、Goランタイムがgoroutineのコンテキストを切り替える際に使用する低レベルの関数です。この関数にuintptr r0
という新しい引数が追加されたことは、Goランタイムが関数呼び出しの際に、より多くのコンテキスト情報を渡す必要があることを示唆しています。gogocall
は、Gobuf
構造体から保存された状態(スタックポインタ、プログラムカウンタなど)を復元し、指定された関数fn
を呼び出します。- 追加された
r0
引数は、アセンブリコード内でDX
(x86)またはR0
(ARM)レジスタにロードされ、最終的にm
構造体のcret
フィールドに保存されます。 m
構造体は、GoランタイムにおけるOSスレッド(M: Machine)の情報を保持しており、cret
フィールドは「call return context」のような意味合いを持つと考えられます。これは、関数呼び出しの戻り値や、呼び出し元のコンテキストに関する追加情報がここに格納されることを示唆しています。runtime·newstack
関数(スタックの拡張が必要な場合に呼び出される)がgogocall
を呼び出す際に、m->cret
を渡すようになったことから、スタックの管理や関数呼び出しの際に、このcret
が重要な役割を果たすことがわかります。
これらの変更は、Go 1.1で導入された新しい関数呼び出し規約(go11func
)の準備段階として行われました。この規約変更は、Goの関数呼び出しのオーバーヘッドを削減し、特にクロージャやインターフェースメソッドの呼び出し性能を向上させることを目的としていました。レジスタの効率的な利用と、関数呼び出しコンテキストのより柔軟な管理は、これらの目標達成に不可欠な要素です。
関連リンク
- Go 1.1 Release Notes: Go 1.1のリリースノートには、このコミットが関連する関数呼び出し規約の変更やパフォーマンス改善に関する情報が含まれている可能性があります。
- Go Assembly Language: Goのアセンブリ言語に関する公式ドキュメントやチュートリアル。
- Go Runtime Source Code: Goランタイムのソースコードは、これらの低レベルな変更の全体像を理解する上で不可欠です。
参考にした情報源リンク
http://golang.org/s/go11func
: このURLは、Go 1.1における関数呼び出し規約の変更に関する提案ドキュメントを指しています。このコミットの背景を深く理解するために最も重要な情報源です。- https://go.dev/s/go11func (リダイレクト後の正しいURL)
- x86 Instruction Set Reference: x86アーキテクチャのレジスタと命令に関する詳細な情報。
- Intel 64 and IA-32 Architectures Software Developer's Manuals (Intelの公式ドキュメント)
- ARM Architecture Reference Manual: ARMアーキテクチャのレジスタと命令に関する詳細な情報。
- ARM Architecture Reference Manual (ARMの公式ドキュメント)
- Goのソースコード: 特に
src/cmd/
,src/pkg/runtime/
以下のファイル。 - GoのIssue Tracker: 関連するバグ報告や機能リクエスト。
- GoのCode Reviewシステム (Gerrit): コミットメッセージに記載されている
https://golang.org/cl/7395050
は、GoのGerritコードレビューシステムへのリンクです。ここには、この変更に関する議論や追加のコンテキストが含まれている可能性があります。- https://go.dev/cl/7395050 (リダイレクト後の正しいURL)
- Goのメーリングリスト (golang-dev): 開発者間の議論。