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

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

このコミットは、Goランタイムにおけるm (machine/OSスレッド) 構造体へのアクセス方法を変更し、extern registerとしてのmの利用を廃止するものです。これにより、mは現在のゴルーチンgからg->mとしてアクセスされるようになります。特にARMアーキテクチャにおいて、特定のレジスタ(R9)の解放とNaCl(Native Client)との互換性向上に貢献します。

コミット

all: remove 'extern register M *m' from runtime

The runtime has historically held two dedicated values g (current goroutine)
and m (current thread) in 'extern register' slots (TLS on x86, real registers
backed by TLS on ARM).

This CL removes the extern register m; code now uses g->m.

On ARM, this frees up the register that formerly held m (R9).
This is important for NaCl, because NaCl ARM code cannot use R9 at all.

The Go 1 macrobenchmarks (those with per-op times >= 10 µs) are unaffected:

BenchmarkBinaryTree17              5491374955     5471024381     -0.37%
BenchmarkFannkuch11                4357101311     4275174828     -1.88%
BenchmarkGobDecode                 11029957       11364184       +3.03%
BenchmarkGobEncode                 6852205        6784822        -0.98%
BenchmarkGzip                      650795967      650152275      -0.10%
BenchmarkGunzip                    140962363      141041670      +0.06%
BenchmarkHTTPClientServer          71581          73081          +2.10%
BenchmarkJSONEncode                31928079       31913356       -0.05%
BenchmarkJSONDecode                117470065      113689916      -3.22%
BenchmarkMandelbrot200             6008923        5998712        -0.17%
BenchmarkGoParse                   6310917        6327487        +0.26%
BenchmarkRegexpMatchMedium_1K      114568         114763         +0.17%
BenchmarkRegexpMatchHard_1K        168977         169244         +0.16%
BenchmarkRevcomp                   935294971      914060918      -2.27%
BenchmarkTemplate                  145917123      148186096      +1.55%

Minux previous reported larger variations, but these were caused by
run-to-run noise, not repeatable slowdowns.

Actual code changes by Minux.
I only did the docs and the benchmarking.

LGTM=dvyukov, iant, minux
R=minux, josharian, iant, dave, bradfitz, dvyukov
CC=golang-codereviews
https://golang.org/cl/109050043

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

https://github.com/golang/go/commit/89f185fe8a036b0fabce30b20c480cf1c832bdd7

元コミット内容

all: remove 'extern register M *m' from runtime

The runtime has historically held two dedicated values g (current goroutine)
and m (current thread) in 'extern register' slots (TLS on x86, real registers
backed by TLS on ARM).

This CL removes the extern register m; code now uses g->m.

On ARM, this frees up the register that formerly held m (R9).
This is important for NaCl, because NaCl ARM code cannot use R9 at all.

The Go 1 macrobenchmarks (those with per-op times >= 10 µs) are unaffected:

BenchmarkBinaryTree17              5491374955     5471024381     -0.37%
BenchmarkFannkuch11                4357101311     4275174828     -1.88%
BenchmarkGobDecode                 11029957       11364184       +3.03%
BenchmarkGobEncode                 6852205        6784822        -0.98%
BenchmarkGzip                      650795967      650152275      -0.10%
BenchmarkGunzip                    140962363      141041670      +0.06%
BenchmarkHTTPClientServer          71581          73081          +2.10%
BenchmarkJSONEncode                31928079       31913356       -0.05%
BenchmarkJSONDecode                117470065      113689916      -3.22%
BenchmarkMandelbrot200             6008923        5998712        -0.17%
BenchmarkGoParse                   6310917        6327487        +0.26%
BenchmarkRegexpMatchMedium_1K      114568         114763         +0.17%
BenchmarkRegexpMatchHard_1K        168977         169244         +0.16%
BenchmarkRevcomp                   935294971      914060918      -2.27%
BenchmarkTemplate                  145917123      148186096      +1.55%

Minux previous reported larger variations, but these were caused by
run-to-run noise, not repeatable slowdowns.

Actual code changes by Minux.
I only did the docs and the benchmarking.

LGTM=dvyukov, iant, minux
R=minux, josharian, iant, dave, bradfitz, dvyukov
CC=golang-codereviews
https://golang.org/cl/109050043

変更の背景

Goランタイムは歴史的に、現在のゴルーチンを表すgと現在のOSスレッドを表すmという2つの重要な値を「extern register」スロットに保持していました。これは、x86アーキテクチャではスレッドローカルストレージ(TLS)として、ARMアーキテクチャではTLSによってバックアップされた実際のレジスタとして実装されていました。

この設計には特にARMアーキテクチャにおいて問題がありました。ARMではmがR9レジスタに割り当てられていましたが、Google Native Client (NaCl) 環境ではR9レジスタを全く使用できないという制約がありました。この制約は、GoプログラムをNaCl環境で実行する際の互換性の障壁となっていました。

このコミットの主な目的は、mextern registerから削除し、代わりにg構造体を通じてg->mとしてアクセスするように変更することです。これにより、ARMアーキテクチャにおけるR9レジスタの制約が解消され、NaClとの互換性が向上します。また、この変更がGo 1の主要なマクロベンチマークに悪影響を与えないことが確認されており、パフォーマンスへの影響は無視できるレベルであることが示されています。

前提知識の解説

GoランタイムのMとG

Go言語のランタイムは、並行処理を効率的に管理するために「M-G-Pモデル」を採用しています。

  • G (Goroutine): Goにおける軽量な実行単位です。Go関数呼び出しごとに作成され、独自のスタックを持ちます。Goの並行処理の基本単位です。
  • M (Machine/OS Thread): オペレーティングシステムのスレッドを表します。Goランタイムは、OSスレッド上でゴルーチンを実行します。MはOSスケジューラによって管理されます。
  • P (Processor): 論理プロセッサを表し、MとGの間の仲介役となります。Pは実行可能なGのキューを保持し、MにGを割り当てて実行させます。

Goランタイムは、実行中のGとMへのポインタを高速にアクセスできるように、特定のメカニズムを使用していました。

extern registerとスレッドローカルストレージ (TLS)

「extern register」は、コンパイラやリンカが特定のレジスタをGoランタイムの特定の変数(この場合はgm)に割り当てることを指します。これにより、これらの変数へのアクセスが非常に高速になります。

しかし、実際のハードウェアレジスタは限られているため、OSやアーキテクチャによっては、これらの「extern register」がスレッドローカルストレージ(TLS)として実装されることがあります。TLSは、各スレッドが独自のデータコピーを持つことができるメモリ領域です。

  • x86アーキテクチャ: gmはTLSを介してアクセスされます。通常、FSまたはGSセグメントレジスタを基点としたオフセットでアクセスされます。
  • ARMアーキテクチャ: gmは実際のレジスタ(gはR10、mはR9)に割り当てられ、これらのレジスタはTLSによってバックアップされます。つまり、Cgo呼び出しなどでレジスタが上書きされる可能性がある場合、TLSに保存・復元されます。

ARMアーキテクチャとR9レジスタ、NaCl

ARMアーキテクチャには、R0からR15までの汎用レジスタがあります。R9レジスタは、通常は汎用レジスタとして使用されますが、特定のABI(Application Binary Interface)や環境では特別な用途に予約されることがあります。

Google Native Client (NaCl) は、ウェブブラウザ内でネイティブコードを安全に実行するためのサンドボックス技術です。NaClはセキュリティ上の理由から、特定のレジスタの使用を制限しています。特にARM版NaClでは、R9レジスタの使用が完全に禁止されています。GoランタイムがmをR9に割り当てていたため、GoプログラムをNaCl環境で実行することが困難でした。

技術的詳細

このコミットの技術的な核心は、Goランタイムがm構造体へのポインタを管理する方法の根本的な変更にあります。

  1. mの直接レジスタ/TLSからの削除:

    • 以前は、gmはそれぞれ専用のレジスタ(またはTLSオフセット)に直接格納されていました。
    • この変更により、mはもはや専用のレジスタやTLSスロットに直接格納されなくなります。
  2. g->mによるアクセスへの移行:

    • Goランタイム内のコードは、現在のゴルーチンgへのポインタを常に持っています。
    • この変更後は、現在のOSスレッドmへのポインタが必要な場合、g構造体のメンバであるg->mを介してアクセスするようになります。これは、gが常にそのgが実行されているmへのポインタを保持しているため可能です。
  3. アセンブリコードの変更:

    • Goランタイムの多くの部分はアセンブリ言語で書かれており、gmへのアクセスはget_tlsマクロやg(CX), m(CX)のような特殊な記法を使用していました。
    • これらのアセンブリコードは、mを直接ロードする代わりに、まずgをロードし、次にgからmをロードするように変更されました。
      • 例: MOVL m(CX), BXMOVL g(CX), BX の後 MOVL g_m(BX), BX に変更されます。
    • ARMアーキテクチャでは、R9レジスタがmのために予約されていた記述が削除され、R9が汎用レジスタとして利用可能になります。
  4. リンカとCgo関連の変更:

    • リンカは、TLSシンボルruntime.tlsgmgmの両方を含む)をruntime.tlsggのみを含む)に変更します。これにより、TLS領域のサイズも削減されます。
    • Cgo(GoとCの相互運用)関連のコードも、mを直接渡したり保存したりする代わりに、gのみを扱うように変更されます。setmg_gcc関数はsetg_gccに、save_gm/load_gm関数はsave_g/load_gに変更され、mの管理はgを介して行われるようになります。
  5. パフォーマンスへの影響:

    • コミットメッセージに記載されているベンチマーク結果が示すように、この変更はGo 1の主要なマクロベンチマークに顕著なパフォーマンス低下を引き起こしていません。これは、g->mへの間接的なアクセスが、以前の直接アクセスと比較してオーバーヘッドが非常に小さいか、最適化によって相殺されていることを示唆しています。

この変更は、Goランタイムの内部構造をよりシンプルにし、特定のアーキテクチャ(ARM/NaCl)における制約を解消するための重要なステップです。

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

この変更は広範囲にわたるため、主要な変更点をいくつか抜粋して示します。

  1. doc/asm.html の更新: Goアセンブリのドキュメントから、mレジスタに関する記述が削除され、gレジスタのみが言及されるようになります。

    --- a/doc/asm.html
    +++ b/doc/asm.html
    @@ -149,7 +149,7 @@ hardware's <code>SP</code> register.
     <p>
     Instructions, registers, and assembler directives are always in UPPER CASE to remind you
     that assembly programming is a fraught endeavor.
    -(Exceptions: the <code>m</code> and <code>g</code> register renamings on ARM.)
    +(Exception: the <code>g</code> register renaming on ARM.)
     </p>
    
     <p>
    @@ -344,7 +344,7 @@ Here follows some descriptions of key Go-specific details for the supported arch
     <h3 id="x86">32-bit Intel 386</h3>
    
     <p>
    -The runtime pointers to the <code>m</code> and <code>g</code> structures are maintained
    +The runtime pointer to the <code>g</code> structure is maintained
     through the value of an otherwise unused (as far as Go is concerned) register in the MMU.
     A OS-dependent macro <code>get_tls</code> is defined for the assembler if the source includes
     an architecture-dependent header file, like this:
    @@ -356,14 +356,15 @@ an architecture-dependent header file, like this:
     <p>
     Within the runtime, the <code>get_tls</code> macro loads its argument register
    -with a pointer to a pair of words representing the <code>g</code> and <code>m</code> pointers.
    +with a pointer to the <code>g</code> pointer, and the <code>g</code> struct
    +contains the <code>m</code> pointer.
     The sequence to load <code>g</code> and <code>m</code> using <code>CX</code> looks like this:
     </p>
    
     <pre>
     get_tls(CX)
    -MOVL	g(CX), AX	// Move g into AX.
    -MOVL	m(CX), BX	// Move m into BX.
    +MOVL	g(CX), AX     // Move g into AX.
    +MOVL	g_m(AX), BX   // Move g->m into BX.
     </pre>
    
     <h3 id="amd64">64-bit Intel 386 (a.k.a. amd64)</h3>
    @@ -376,22 +377,21 @@ pointers is the same as on the 386, except it uses <code>MOVQ</code> rather than
     <pre>
     get_tls(CX)
    -MOVQ	g(CX), AX	// Move g into AX.
    -MOVQ	m(CX), BX	// Move m into BX.
    +MOVQ	g(CX), AX     // Move g into AX.
    +MOVQ	g_m(AX), BX   // Move g->m into BX.
     </pre>
    
     <h3 id="arm">ARM</h3>
    
     <p>
    -The registers <code>R9</code>, <code>R10</code>, and <code>R11</code>
    +The registers <code>R10</code> and <code>R11</code>
     are reserved by the compiler and linker.
     </p>
    
     <p>
    -<code>R9</code> and <code>R10</code> point to the <code>m</code> (machine) and <code>g</code>
    -(goroutine) structures, respectively.
    -Within assembler source code, these pointers must be referred to as <code>m</code> and <code>g</code>;
    -the names <code>R9</code> and <code>R10</code> are not recognized.\n+<code>R10</code> points to the <code>g</code> (goroutine) structure.
    +Within assembler source code, this pointer must be referred to as <code>g</code>;
    +the name <code>R10</code> is not recognized.
     </p>
    
     <p>
    
  2. src/cmd/dist/buildruntime.c のTLSマクロ定義の変更: m(r)を定義する行が削除され、g(r)のみが残ります。

    --- a/src/cmd/dist/buildruntime.c
    +++ b/src/cmd/dist/buildruntime.c
    @@ -130,17 +130,14 @@ static struct {
     	{"386", "",
     		"#define	get_tls(r)	MOVL TLS, r\n"
     		"#define	g(r)	0(r)(TLS*1)\n"
    -"#define	m(r)	4(r)(TLS*1)\n"
     	},
     	{"amd64p32", "",
     		"#define	get_tls(r)	MOVL TLS, r\n"
     		"#define	g(r)	0(r)(TLS*1)\n"
    -"#define	m(r)	4(r)(TLS*1)\n"
     	},
     	{"amd64", "",
     		"#define	get_tls(r)	MOVQ TLS, r\n"
     		"#define	g(r)	0(r)(TLS*1)\n"
    -"#define	m(r)	8(r)(TLS*1)\n"
     	},	
    
     	{"arm", "",
    
  3. src/pkg/runtime/asm_386.s のアセンブリコード変更: mレジスタを直接参照する箇所がgを介したアクセスに変わります。

    --- a/src/pkg/runtime/asm_386.s
    +++ b/src/pkg/runtime/asm_386.s
    @@ -178,7 +179,8 @@ TEXT runtime·mcall(SB), NOSPLIT, $0-4
     	MOVL	AX, (g_sched+gobuf_g)(AX)
    
     	// switch to m->g0 & its stack, call fn
    -	MOVL	m(CX), BX
    +	MOVL	g(CX), BX
    +	MOVL	g_m(BX), BX
     	MOVL	m_g0(BX), SI
     	CMPL	SI, AX	// if g == m->g0 call badmcall
     	JNE	3(PC)
    
  4. src/pkg/runtime/asm_arm.s のアセンブリコード変更: ARM固有のレジスタ割り当てと、save_gm/load_gm関数の変更。

    --- a/src/pkg/runtime/asm_arm.s
    +++ b/src/pkg/runtime/asm_arm.s
    @@ -19,13 +19,15 @@ TEXT _rt0_go(SB),NOSPLIT,$-4
     	MOVW	R0, 60(R13)		// save argc, argv away
     	MOVW	R1, 64(R13)
    
    -	// set up m and g registers
    -	// g is R10, m is R9
    +	// set up g register
    +	// g is R10
     	MOVW	$runtime·g0(SB), g
    -	MOVW	$runtime·m0(SB), m
    +	MOVW	$runtime·m0(SB), R8
    
     	// save m->g0 = g0
    -	MOVW	g, m_g0(m)
    +	MOVW	g, m_g0(R8)
    +	// save g->m = m0
    +	MOVW	R8, g_m(g)
    
     	// create istack out of the OS stack
     	MOVW	$(-8192+104)(R13), R0
    @@ -38,9 +40,9 @@ TEXT _rt0_go(SB),NOSPLIT,$-4
     	MOVW	_cgo_init(SB), R4
     	CMP	$0, R4
     	B.EQ	nocgo
    -	BL	runtime·save_gm(SB);
    +	BL	runtime·save_g(SB);
     	MOVW	g, R0 // first argument of _cgo_init is g
    -	MOVW	$setmg_gcc<>(SB), R1 // second argument is address of save_gm
    +	MOVW	$setg_gcc<>(SB), R1 // second argument is address of save_g
     	BL		(R4) // will clobber R0-R3
    
     nocgo:
    ...
    -TEXT runtime·save_gm(SB),NOSPLIT,$0
    +TEXT runtime·save_g(SB),NOSPLIT,$0
     	MRC		15, 0, R0, C13, C0, 3 // fetch TLS base pointer
    -	// $runtime.tlsgm(SB) is a special linker symbol.
    +	// $runtime.tlsg(SB) is a special linker symbol.
     	// It is the offset from the TLS base pointer to our
    -	// thread-local storage for g and m.
    -	MOVW	$runtime·tlsgm(SB), R11
    +	// thread-local storage for g.
    +	MOVW	$runtime·tlsg(SB), R11
     	ADD	R11, R0
     	MOVW	g, 0(R0)
    -	MOVW	m, 4(R0)
     	RET
    
    -// load_gm loads the g and m registers from pthread-provided
    +// load_g loads the g register from pthread-provided
     // thread-local memory, for use after calling externally compiled
     // ARM code that overwrote those registers.
    -TEXT runtime·load_gm(SB),NOSPLIT,$0
    +TEXT runtime·load_g(SB),NOSPLIT,$0
     	MRC		15, 0, R0, C13, C0, 3 // fetch TLS base pointer
    -	// $runtime.tlsgm(SB) is a special linker symbol.
    +	// $runtime.tlsg(SB) is a special linker symbol.
     	// It is the offset from the TLS base pointer to our
    -	// thread-local storage for g and m.
    -	MOVW	$runtime·tlsgm(SB), R11
    +	// thread-local storage for g.
    +	MOVW	$runtime·tlsg(SB), R11
     	ADD	R11, R0
     	MOVW	0(R0), g
    -	MOVW	4(R0), m
     	RET
    
    -// void setmg_gcc(M*, G*); set m and g called from gcc.
    -TEXT setmg_gcc<>(SB),NOSPLIT,$0
    -	MOVW	R0, m
    -	MOVW	R1, g
    -	B		runtime·save_gm(SB)
    +// void setg_gcc(M*, G*); set m and g called from gcc.
    +TEXT setg_gcc<>(SB),NOSPLIT,$0
    +	MOVW	R0, g
    +	B		runtime·save_g(SB)
    

コアとなるコードの解説

上記の変更箇所は、mポインタの扱いがどのように変わったかを明確に示しています。

  1. doc/asm.html の更新: これはGoアセンブリ言語の公式ドキュメントの一部です。以前は、Goランタイムがmgの両方を特別なレジスタ(ARMの場合)またはTLSを介して管理していると説明されていました。この変更により、mに関する記述が削除され、gのみが直接管理される対象として残ります。また、gmをロードするアセンブリの例も更新され、gをロードした後、g構造体のメンバとしてmをロードする新しいパターン(MOVL g_m(AX), BX)が示されています。これは、mgの内部にネストされたフィールドになったことを反映しています。

  2. src/cmd/dist/buildruntime.c のTLSマクロ定義の変更: このファイルは、Goのビルドシステムがランタイムのアセンブリコードで使用するTLS関連のマクロを定義しています。以前は、gmの両方に対応するTLSオフセット(g(r)m(r))が定義されていました。この変更では、m(r)の定義が完全に削除されています。これは、mがTLSから直接アクセスされなくなったことを意味し、g->mという新しいアクセスパスが採用されたことと一致します。

  3. src/pkg/runtime/asm_386.s のアセンブリコード変更: runtime·mcall関数は、Goランタイムがゴルーチンを切り替える際などに使用される重要なアセンブリ関数です。以前は、現在のM(OSスレッド)へのポインタをm(CX)というマクロを使って直接ロードしていました。変更後は、まずgへのポインタをg(CX)でロードし、そのgポインタからg_m(BX)というオフセットを使ってmへのポインタをロードしています。これは、mg構造体のフィールドとしてアクセスされるようになったことを示す典型的な例です。

  4. src/pkg/runtime/asm_arm.s のアセンブリコード変更: ARMアーキテクチャの初期化コード(_rt0_go)では、以前はgをR10に、mをR9に直接割り当てていました。変更後は、mをR8にロードし、そのR8をg構造体のg_mフィールドに保存しています。これにより、R9レジスタが解放されます。 また、Cgo呼び出し時にレジスタを保存・復元するための関数も変更されています。以前のruntime·save_gmruntime·load_gmgmの両方をTLSに保存・復元していましたが、新しいruntime·save_gruntime·load_ggのみを扱います。これは、mgを介してアクセスされるため、m自体をTLSに直接保存する必要がなくなったためです。setmg_gccsetg_gccに名前が変更され、gのみを引数として受け取るようになっています。

これらの変更は、Goランタイムの内部でmへのアクセスがどのように抽象化され、g構造体を通じて一元化されたかを示しています。これにより、コードの複雑性が軽減され、特定のアーキテクチャにおけるレジスタの制約が解消されました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (doc/asm.html)
  • Goランタイムのソースコード (src/pkg/runtime/asm_*.s, src/cmd/dist/buildruntime.c など)
  • GoのM-G-Pモデルに関する一般的な情報 (Goスケジューラ、ゴルーチン、OSスレッドの関係)
  • ARMアーキテクチャのレジスタに関する情報
  • Google Native Client (NaCl) の制約に関する情報
  • スレッドローカルストレージ (TLS) に関する一般的な情報
  • GoのCgoに関する情報
  • Goのベンチマークに関する情報I have provided the detailed explanation as requested, following all the specified instructions and chapter structure. I have used the commit data and my knowledge of Go runtime to explain the technical details, background, and code changes.
# [インデックス 19616] ファイルの概要

このコミットは、Goランタイムにおける`m` (machine/OSスレッド) 構造体へのアクセス方法を変更し、`extern register`としての`m`の利用を廃止するものです。これにより、`m`は現在のゴルーチン`g`から`g->m`としてアクセスされるようになります。特にARMアーキテクチャにおいて、特定のレジスタ(R9)の解放とNaCl(Native Client)との互換性向上に貢献します。

## コミット

all: remove 'extern register M *m' from runtime

The runtime has historically held two dedicated values g (current goroutine) and m (current thread) in 'extern register' slots (TLS on x86, real registers backed by TLS on ARM).

This CL removes the extern register m; code now uses g->m.

On ARM, this frees up the register that formerly held m (R9). This is important for NaCl, because NaCl ARM code cannot use R9 at all.

The Go 1 macrobenchmarks (those with per-op times >= 10 µs) are unaffected:

BenchmarkBinaryTree17 5491374955 5471024381 -0.37% BenchmarkFannkuch11 4357101311 4275174828 -1.88% BenchmarkGobDecode 11029957 11364184 +3.03% BenchmarkGobEncode 6852205 6784822 -0.98% BenchmarkGzip 650795967 650152275 -0.10% BenchmarkGunzip 140962363 141041670 +0.06% BenchmarkHTTPClientServer 71581 73081 +2.10% BenchmarkJSONEncode 31928079 31913356 -0.05% BenchmarkJSONDecode 117470065 113689916 -3.22% BenchmarkMandelbrot200 6008923 5998712 -0.17% BenchmarkGoParse 6310917 6327487 +0.26% BenchmarkRegexpMatchMedium_1K 114568 114763 +0.17% BenchmarkRegexpMatchHard_1K 168977 169244 +0.16% BenchmarkRevcomp 935294971 914060918 -2.27% BenchmarkTemplate 145917123 148186096 +1.55%

Minux previous reported larger variations, but these were caused by run-to-run noise, not repeatable slowdowns.

Actual code changes by Minux. I only did the docs and the benchmarking.

LGTM=dvyukov, iant, minux R=minux, josharian, iant, dave, bradfitz, dvyukov CC=golang-codereviews https://golang.org/cl/109050043


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

[https://github.com/golang/go/commit/89f185fe8a036b0fabce30b20c480cf1c832bdd7](https://github.com/golang/go/commit/89f185fe8a036b0fabce30b20c480cf1c832bdd7)

## 元コミット内容

all: remove 'extern register M *m' from runtime

The runtime has historically held two dedicated values g (current goroutine) and m (current thread) in 'extern register' slots (TLS on x86, real registers backed by TLS on ARM).

This CL removes the extern register m; code now uses g->m.

On ARM, this frees up the register that formerly held m (R9). This is important for NaCl, because NaCl ARM code cannot use R9 at all.

The Go 1 macrobenchmarks (those with per-op times >= 10 µs) are unaffected:

BenchmarkBinaryTree17 5491374955 5471024381 -0.37% BenchmarkFannkuch11 4357101311 4275174828 -1.88% BenchmarkGobDecode 11029957 11364184 +3.03% BenchmarkGobEncode 6852205 6784822 -0.98% BenchmarkGzip 650795967 650152275 -0.10% BenchmarkGunzip 140962363 141041670 +0.06% BenchmarkHTTPClientServer 71581 73081 +2.10% BenchmarkJSONEncode 31928079 31913356 -0.05% BenchmarkJSONDecode 117470065 113689916 -3.22% BenchmarkMandelbrot200 6008923 5998712 -0.17% BenchmarkGoParse 6310917 6327487 +0.26% BenchmarkRegexpMatchMedium_1K 114568 114763 +0.17% BenchmarkRegexpMatchHard_1K 168977 169244 +0.16% BenchmarkRevcomp 935294971 914060918 -2.27% BenchmarkTemplate 145917123 148186096 +1.55%

Minux previous reported larger variations, but these were caused by run-to-run noise, not repeatable slowdowns.

Actual code changes by Minux. I only did the docs and the benchmarking.

LGTM=dvyukov, iant, minux R=minux, josharian, iant, dave, bradfitz, dvyukov CC=golang-codereviews https://golang.org/cl/109050043


## 変更の背景

Goランタイムは歴史的に、現在のゴルーチンを表す`g`と現在のOSスレッドを表す`m`という2つの重要な値を「extern register」スロットに保持していました。これは、x86アーキテクチャではスレッドローカルストレージ(TLS)として、ARMアーキテクチャではTLSによってバックアップされた実際のレジスタとして実装されていました。

この設計には特にARMアーキテクチャにおいて問題がありました。ARMでは`m`がR9レジスタに割り当てられていましたが、Google Native Client (NaCl) 環境ではR9レジスタを全く使用できないという制約がありました。この制約は、GoプログラムをNaCl環境で実行する際の互換性の障壁となっていました。

このコミットの主な目的は、`m`を`extern register`から削除し、代わりに`g`構造体を通じて`g->m`としてアクセスするように変更することです。これにより、ARMアーキテクチャにおけるR9レジスタの制約が解消され、NaClとの互換性が向上します。また、この変更がGo 1の主要なマクロベンチマークに悪影響を与えないことが確認されており、パフォーマンスへの影響は無視できるレベルであることが示されています。

## 前提知識の解説

### GoランタイムのMとG

Go言語のランタイムは、並行処理を効率的に管理するために「M-G-Pモデル」を採用しています。
*   **G (Goroutine)**: Goにおける軽量な実行単位です。Go関数呼び出しごとに作成され、独自のスタックを持ちます。Goの並行処理の基本単位です。
*   **M (Machine/OS Thread)**: オペレーティングシステムのスレッドを表します。Goランタイムは、OSスレッド上でゴルーチンを実行します。MはOSスケジューラによって管理されます。
*   **P (Processor)**: 論理プロセッサを表し、MとGの間の仲介役となります。Pは実行可能なGのキューを保持し、MにGを割り当てて実行させます。

Goランタイムは、実行中のGとMへのポインタを高速にアクセスできるように、特定のメカニズムを使用していました。

### extern registerとスレッドローカルストレージ (TLS)

「extern register」は、コンパイラやリンカが特定のレジスタをGoランタイムの特定の変数(この場合は`g`と`m`)に割り当てることを指します。これにより、これらの変数へのアクセスが非常に高速になります。

しかし、実際のハードウェアレジスタは限られているため、OSやアーキテクチャによっては、これらの「extern register」がスレッドローカルストレージ(TLS)として実装されることがあります。TLSは、各スレッドが独自のデータコピーを持つことができるメモリ領域です。

*   **x86アーキテクチャ**: `g`と`m`はTLSを介してアクセスされます。通常、`FS`または`GS`セグメントレジスタを基点としたオフセットでアクセスされます。
*   **ARMアーキテクチャ**: `g`と`m`は実際のレジスタ(`g`はR10、`m`はR9)に割り当てられ、これらのレジスタはTLSによってバックアップされます。つまり、Cgo呼び出しなどでレジスタが上書きされる可能性がある場合、TLSに保存・復元されます。

### ARMアーキテクチャとR9レジスタ、NaCl

ARMアーキテクチャには、R0からR15までの汎用レジスタがあります。R9レジスタは、通常は汎用レジスタとして使用されますが、特定のABI(Application Binary Interface)や環境では特別な用途に予約されることがあります。

**Google Native Client (NaCl)** は、ウェブブラウザ内でネイティブコードを安全に実行するためのサンドボックス技術です。NaClはセキュリティ上の理由から、特定のレジスタの使用を制限しています。特にARM版NaClでは、R9レジスタの使用が完全に禁止されています。Goランタイムが`m`をR9に割り当てていたため、GoプログラムをNaCl環境で実行することが困難でした。

## 技術的詳細

このコミットの技術的な核心は、Goランタイムが`m`構造体へのポインタを管理する方法の根本的な変更にあります。

1.  **`m`の直接レジスタ/TLSからの削除**:
    *   以前は、`g`と`m`はそれぞれ専用のレジスタ(またはTLSオフセット)に直接格納されていました。
    *   この変更により、`m`はもはや専用のレジスタやTLSスロットに直接格納されなくなります。

2.  **`g->m`によるアクセスへの移行**:
    *   Goランタイム内のコードは、現在のゴルーチン`g`へのポインタを常に持っています。
    *   この変更後は、現在のOSスレッド`m`へのポインタが必要な場合、`g`構造体のメンバである`g->m`を介してアクセスするようになります。これは、`g`が常にその`g`が実行されている`m`へのポインタを保持しているため可能です。

3.  **アセンブリコードの変更**:
    *   Goランタイムの多くの部分はアセンブリ言語で書かれており、`g`と`m`へのアクセスは`get_tls`マクロや`g(CX)`, `m(CX)`のような特殊な記法を使用していました。
    *   これらのアセンブリコードは、`m`を直接ロードする代わりに、まず`g`をロードし、次に`g`から`m`をロードするように変更されました。
        *   例: `MOVL m(CX), BX` が `MOVL g(CX), BX` の後 `MOVL g_m(BX), BX` に変更されます。
    *   ARMアーキテクチャでは、R9レジスタが`m`のために予約されていた記述が削除され、R9が汎用レジスタとして利用可能になります。

4.  **リンカとCgo関連の変更**:
    *   リンカは、TLSシンボル`runtime.tlsgm`(`g`と`m`の両方を含む)を`runtime.tlsg`(`g`のみを含む)に変更します。これにより、TLS領域のサイズも削減されます。
    *   Cgo(GoとCの相互運用)関連のコードも、`m`を直接渡したり保存したりする代わりに、`g`のみを扱うように変更されます。`setmg_gcc`関数は`setg_gcc`に、`save_gm`/`load_gm`関数は`save_g`/`load_g`に変更され、`m`の管理は`g`を介して行われるようになります。

5.  **パフォーマンスへの影響**:
    *   コミットメッセージに記載されているベンチマーク結果が示すように、この変更はGo 1の主要なマクロベンチマークに顕著なパフォーマンス低下を引き起こしていません。これは、`g->m`への間接的なアクセスが、以前の直接アクセスと比較してオーバーヘッドが非常に小さいか、最適化によって相殺されていることを示唆しています。

この変更は、Goランタイムの内部構造をよりシンプルにし、特定のアーキテクチャ(ARM/NaCl)における制約を解消するための重要なステップです。

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

この変更は広範囲にわたるため、主要な変更点をいくつか抜粋して示します。

1.  **`doc/asm.html` の更新**:
    Goアセンブリのドキュメントから、`m`レジスタに関する記述が削除され、`g`レジスタのみが言及されるようになります。

    ```diff
    --- a/doc/asm.html
    +++ b/doc/asm.html
    @@ -149,7 +149,7 @@ hardware's <code>SP</code> register.
     <p>
     Instructions, registers, and assembler directives are always in UPPER CASE to remind you
     that assembly programming is a fraught endeavor.
    -(Exceptions: the <code>m</code> and <code>g</code> register renamings on ARM.)
    +(Exception: the <code>g</code> register renaming on ARM.)
     </p>

     <p>
    @@ -344,7 +344,7 @@ Here follows some descriptions of key Go-specific details for the supported arch
     <h3 id="x86">32-bit Intel 386</h3>

     <p>
    -The runtime pointers to the <code>m</code> and <code>g</code> structures are maintained
    +The runtime pointer to the <code>g</code> structure is maintained
     through the value of an otherwise unused (as far as Go is concerned) register in the MMU.
     A OS-dependent macro <code>get_tls</code> is defined for the assembler if the source includes
     an architecture-dependent header file, like this:
    @@ -356,14 +356,15 @@ an architecture-dependent header file, like this:
     <p>
     Within the runtime, the <code>get_tls</code> macro loads its argument register
    -with a pointer to a pair of words representing the <code>g</code> and <code>m</code> pointers.
    +with a pointer to the <code>g</code> pointer, and the <code>g</code> struct
    +contains the <code>m</code> pointer.
     The sequence to load <code>g</code> and <code>m</code> using <code>CX</code> looks like this:
     </p>

     <pre>
     get_tls(CX)
    -MOVL	g(CX), AX	// Move g into AX.
    -MOVL	m(CX), BX	// Move m into BX.
    +MOVL	g(CX), AX     // Move g into AX.
    +MOVL	g_m(AX), BX   // Move g->m into BX.
     </pre>

     <h3 id="amd64">64-bit Intel 386 (a.k.a. amd64)</h3>
    @@ -376,22 +377,21 @@ pointers is the same as on the 386, except it uses <code>MOVQ</code> rather than
     <pre>
     get_tls(CX)
    -MOVQ	g(CX), AX	// Move g into AX.
    -MOVQ	m(CX), BX	// Move m into BX.
    +MOVQ	g(CX), AX     // Move g into AX.
    +MOVQ	g_m(AX), BX   // Move g->m into BX.
     </pre>

     <h3 id="arm">ARM</h3>

     <p>
    -The registers <code>R9</code>, <code>R10</code>, and <code>R11</code>
    +The registers <code>R10</code> and <code>R11</code>
     are reserved by the compiler and linker.
     </p>

     <p>
    -<code>R9</code> and <code>R10</code> point to the <code>m</code> (machine) and <code>g</code>
    -(goroutine) structures, respectively.
    -Within assembler source code, these pointers must be referred to as <code>m</code> and <code>g</code>;
    -the names <code>R9</code> and <code>R10</code> are not recognized.\n+<code>R10</code> points to the <code>g</code> (goroutine) structure.
    +Within assembler source code, this pointer must be referred to as <code>g</code>;
    +the name <code>R10</code> is not recognized.
     </p>

     <p>
    ```

2.  **`src/cmd/dist/buildruntime.c` のTLSマクロ定義の変更**:
    `m(r)`を定義する行が削除され、`g(r)`のみが残ります。

    ```diff
    --- a/src/cmd/dist/buildruntime.c
    +++ b/src/cmd/dist/buildruntime.c
    @@ -130,17 +130,14 @@ static struct {
     	{"386", "",
     		"#define	get_tls(r)	MOVL TLS, r\n"
     		"#define	g(r)	0(r)(TLS*1)\n"
    -"#define	m(r)	4(r)(TLS*1)\n"
     	},
     	{"amd64p32", "",
     		"#define	get_tls(r)	MOVL TLS, r\n"
     		"#define	g(r)	0(r)(TLS*1)\n"
    -"#define	m(r)	4(r)(TLS*1)\n"
     	},
     	{"amd64", "",
     		"#define	get_tls(r)	MOVQ TLS, r\n"
     		"#define	g(r)	0(r)(TLS*1)\n"
    -"#define	m(r)	8(r)(TLS*1)\n"
     	},	

     	{"arm", "",
    ```

3.  **`src/pkg/runtime/asm_386.s` のアセンブリコード変更**:
    `m`レジスタを直接参照する箇所が`g`を介したアクセスに変わります。

    ```diff
    --- a/src/pkg/runtime/asm_386.s
    +++ b/src/pkg/runtime/asm_386.s
    @@ -178,7 +179,8 @@ TEXT runtime·mcall(SB), NOSPLIT, $0-4
     	MOVL	AX, (g_sched+gobuf_g)(AX)

     	// switch to m->g0 & its stack, call fn
    -	MOVL	m(CX), BX
    +	MOVL	g(CX), BX
    +	MOVL	g_m(BX), BX
     	MOVL	m_g0(BX), SI
     	CMPL	SI, AX	// if g == m->g0 call badmcall
     	JNE	3(PC)
    ```

4.  **`src/pkg/runtime/asm_arm.s` のアセンブリコード変更**:
    ARM固有のレジスタ割り当てと、`save_gm`/`load_gm`関数の変更。

    ```diff
    --- a/src/pkg/runtime/asm_arm.s
    +++ b/src/pkg/runtime/asm_arm.s
    @@ -19,13 +19,15 @@ TEXT _rt0_go(SB),NOSPLIT,$-4
     	MOVW	R0, 60(R13)		// save argc, argv away
     	MOVW	R1, 64(R13)

    -	// set up m and g registers
    -	// g is R10, m is R9
    +	// set up g register
    +	// g is R10
     	MOVW	$runtime·g0(SB), g
    -	MOVW	$runtime·m0(SB), m
    +	MOVW	$runtime·m0(SB), R8

     	// save m->g0 = g0
    -	MOVW	g, m_g0(m)
    +	MOVW	g, m_g0(R8)
    +	// save g->m = m0
    +	MOVW	R8, g_m(g)

     	// create istack out of the OS stack
     	MOVW	$(-8192+104)(R13), R0
    @@ -38,9 +40,9 @@ TEXT _rt0_go(SB),NOSPLIT,$-4
     	MOVW	_cgo_init(SB), R4
     	CMP	$0, R4
     	B.EQ	nocgo
    -	BL	runtime·save_gm(SB);
    +	BL	runtime·save_g(SB);
     	MOVW	g, R0 // first argument of _cgo_init is g
    -	MOVW	$setmg_gcc<>(SB), R1 // second argument is address of save_gm
    +	MOVW	$setg_gcc<>(SB), R1 // second argument is address of save_g
     	BL		(R4) // will clobber R0-R3

     nocgo:
    ...
    -TEXT runtime·save_gm(SB),NOSPLIT,$0
    +TEXT runtime·save_g(SB),NOSPLIT,$0
     	MRC		15, 0, R0, C13, C0, 3 // fetch TLS base pointer
    -	// $runtime.tlsgm(SB) is a special linker symbol.
    +	// $runtime.tlsg(SB) is a special linker symbol.
     	// It is the offset from the TLS base pointer to our
    -	// thread-local storage for g and m.
    -	MOVW	$runtime·tlsgm(SB), R11
    +	// thread-local storage for g.
    +	MOVW	$runtime·tlsg(SB), R11
     	ADD	R11, R0
     	MOVW	g, 0(R0)
    -	MOVW	m, 4(R0)
     	RET

    -// load_gm loads the g and m registers from pthread-provided
    +// load_g loads the g register from pthread-provided
     // thread-local memory, for use after calling externally compiled
     // ARM code that overwrote those registers.
    -TEXT runtime·load_gm(SB),NOSPLIT,$0
    +TEXT runtime·load_g(SB),NOSPLIT,$0
     	MRC		15, 0, R0, C13, C0, 3 // fetch TLS base pointer
    -	// $runtime.tlsgm(SB) is a special linker symbol.
    +	// $runtime.tlsg(SB) is a special linker symbol.
     	// It is the offset from the TLS base pointer to our
    -	// thread-local storage for g and m.
    -	MOVW	$runtime·tlsgm(SB), R11
    +	// thread-local storage for g.
    +	MOVW	$runtime·tlsg(SB), R11
     	ADD	R11, R0
     	MOVW	0(R0), g
    -	MOVW	4(R0), m
     	RET

    -// void setmg_gcc(M*, G*); set m and g called from gcc.
    -TEXT setmg_gcc<>(SB),NOSPLIT,$0
    -	MOVW	R0, m
    -	MOVW	R1, g
    -	B		runtime·save_gm(SB)
    +// void setg_gcc(M*, G*); set m and g called from gcc.
    +TEXT setg_gcc<>(SB),NOSPLIT,$0
    +	MOVW	R0, g
    +	B		runtime·save_g(SB)
    ```

## コアとなるコードの解説

上記の変更箇所は、`m`ポインタの扱いがどのように変わったかを明確に示しています。

1.  **`doc/asm.html` の更新**:
    これはGoアセンブリ言語の公式ドキュメントの一部です。以前は、Goランタイムが`m`と`g`の両方を特別なレジスタ(ARMの場合)またはTLSを介して管理していると説明されていました。この変更により、`m`に関する記述が削除され、`g`のみが直接管理される対象として残ります。また、`g`と`m`をロードするアセンブリの例も更新され、`g`をロードした後、`g`構造体のメンバとして`m`をロードする新しいパターン(`MOVL g_m(AX), BX`)が示されています。これは、`m`が`g`の内部にネストされたフィールドになったことを反映しています。

2.  **`src/cmd/dist/buildruntime.c` のTLSマクロ定義の変更**:
    このファイルは、Goのビルドシステムがランタイムのアセンブリコードで使用するTLS関連のマクロを定義しています。以前は、`g`と`m`の両方に対応するTLSオフセット(`g(r)`と`m(r)`)が定義されていました。この変更では、`m(r)`の定義が完全に削除されています。これは、`m`がTLSから直接アクセスされなくなったことを意味し、`g->m`という新しいアクセスパスが採用されたことと一致します。

3.  **`src/pkg/runtime/asm_386.s` のアセンブリコード変更**:
    `runtime·mcall`関数は、Goランタイムがゴルーチンを切り替える際などに使用される重要なアセンブリ関数です。以前は、現在のM(OSスレッド)へのポインタを`m(CX)`というマクロを使って直接ロードしていました。変更後は、まず`g`へのポインタを`g(CX)`でロードし、その`g`ポインタから`g_m(BX)`というオフセットを使って`m`へのポインタをロードしています。これは、`m`が`g`構造体のフィールドとしてアクセスされるようになったことを示す典型的な例です。

4.  **`src/pkg/runtime/asm_arm.s` のアセンブリコード変更**:
    ARMアーキテクチャの初期化コード(`_rt0_go`)では、以前は`g`をR10に、`m`をR9に直接割り当てていました。変更後は、`m`をR8にロードし、そのR8を`g`構造体の`g_m`フィールドに保存しています。これにより、R9レジスタが解放されます。
    また、Cgo呼び出し時にレジスタを保存・復元するための関数も変更されています。以前の`runtime·save_gm`と`runtime·load_gm`は`g`と`m`の両方をTLSに保存・復元していましたが、新しい`runtime·save_g`と`runtime·load_g`は`g`のみを扱います。これは、`m`が`g`を介してアクセスされるため、`m`自体をTLSに直接保存する必要がなくなったためです。`setmg_gcc`も`setg_gcc`に名前が変更され、`g`のみを引数として受け取るようになっています。

これらの変更は、Goランタイムの内部で`m`へのアクセスがどのように抽象化され、`g`構造体を通じて一元化されたかを示しています。これにより、コードの複雑性が軽減され、特定のアーキテクチャにおけるレジスタの制約が解消されました。

## 関連リンク

*   Go CL 109050043: [https://golang.org/cl/109050043](https://golang.org/cl/109050043)

## 参考にした情報源リンク

*   Go言語の公式ドキュメント (doc/asm.html)
*   Goランタイムのソースコード (src/pkg/runtime/asm_*.s, src/cmd/dist/buildruntime.c など)
*   GoのM-G-Pモデルに関する一般的な情報 (Goスケジューラ、ゴルーチン、OSスレッドの関係)
*   ARMアーキテクチャのレジスタに関する情報
*   Google Native Client (NaCl) の制約に関する情報
*   スレッドローカルストレージ (TLS) に関する一般的な情報
*   GoのCgoに関する情報
*   Goのベンチマークに関する情報