[インデックス 16436] ファイルの概要
このコミットは、Go言語のARMアーキテクチャ向けアセンブラおよびランタイムにおけるレジスタの使用方法に関する重要な変更を導入しています。具体的には、アセンブラがレジスタR9とR10を直接参照するサポートを廃止し、代わりにGoランタイムが内部的に使用するm
(現在のM、マシン)とg
(現在のG、ゴルーチン)という擬似レジスタ名を使用するように変更しています。これにより、意図しないレジスタの破壊(clobber)を防ぎ、より堅牢なコード生成を目指しています。
コミット
commit 5d081792b6990ecb34346573d351b8a4d530270e
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Thu May 30 03:03:52 2013 +0800
cmd/5a, cmd/dist, runtime: support m/g in the assembler, drop support for R9/R10
to avoid unintentionally clobber R9/R10.
Thanks Lucio for the suggestion.
PS: yes, this could be considered a big change (but not an API change), but
as it turns out even temporarily changes R9/R10 in user code is unsafe and
leads to very hard to diagnose problems later, better to disable using R9/R10
when the user first uses it.
See CL 6300043 and CL 6305100 for two problems caused by misusing R9/R10.
R=golang-dev, khr, rsc
CC=golang-dev
https://golang.org/cl/9840043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5d081792b6990ecb34346573d351b8a4d530270e
元コミット内容
cmd/5a
, cmd/dist
, runtime
: アセンブラで m/g
をサポートし、R9/R10 のサポートを廃止。これは、R9/R10 を意図せず破壊するのを避けるため。Lucio の提案に感謝します。
追記: はい、これは大きな変更と見なされるかもしれませんが(APIの変更ではありません)、ユーザーコードで一時的にR9/R10を変更することさえ安全ではなく、後で診断が非常に困難な問題につながることが判明したため、ユーザーがR9/R10を初めて使用する際に、その使用を無効にすることが望ましいです。R9/R10の誤用によって引き起こされた2つの問題については、CL 6300043 と CL 6305100 を参照してください。
変更の背景
この変更の背景には、GoランタイムがARMアーキテクチャ上で特定のレジスタ(R9とR10)を内部的にm
(マシン、現在のOSスレッドに紐づくGoのスケジューラコンテキスト)とg
(ゴルーチン、現在の実行中のゴルーチン)という重要なポインタとして使用しているという事実があります。
Goのランタイムは、効率的なゴルーチン切り替えやスケジューリングのために、これらのレジスタに現在のm
とg
のポインタを保持しています。しかし、Goのアセンブラ(cmd/5a
)がR9とR10を汎用レジスタとして直接参照することを許可していたため、ユーザーが書いたアセンブリコードや、コンパイラが生成したコードが、これらのレジスタを意図せず上書き(clobber)してしまう可能性がありました。
コミットメッセージで言及されているように、このような意図しないレジスタの破壊は、非常に診断が困難なバグを引き起こすことが判明しました。特に、CL 6300043とCL 6305100で具体的な問題が報告されており、これはGoのランタイムの安定性と信頼性に直接影響を与えていました。
この問題を解決するため、Go開発チームは、アセンブラレベルでR9とR10の直接使用を禁止し、代わりにランタイムのセマンティクスを反映したm
とg
というシンボリックな名前を導入することを決定しました。これにより、アセンブリコードを書く開発者は、これらのレジスタがランタイムによって特殊な目的で使用されていることを明確に認識し、誤用を防ぐことができます。
前提知識の解説
ARMアーキテクチャのレジスタ
ARMアーキテクチャは、汎用レジスタ(R0-R12)、スタックポインタ(SP/R13)、リンクレジスタ(LR/R14)、プログラムカウンタ(PC/R15)など、複数のレジスタを持っています。これらのレジスタは、命令のオペランドとして使用され、データの保持やアドレスの計算に利用されます。
- R0-R12: 汎用レジスタ。通常、関数呼び出しの引数や戻り値、一時的なデータの保存などに使用されます。
- R9, R10: 汎用レジスタの一部ですが、特定のABI(Application Binary Interface)やOS、ランタイムによっては特殊な用途に割り当てられることがあります。例えば、スレッドローカルストレージのポインタや、特定のシステムコールで使用されることがあります。
レジスタの「clobber」
「clobber」(クローバー)とは、プログラミング、特にアセンブリ言語やコンパイラの文脈で、あるレジスタに格納されている値が、意図せず別の操作によって上書きされてしまうことを指します。これは、関数呼び出し規約(ABI)に従わずにレジスタを使用したり、特定のレジスタがランタイムによって特殊な目的で使用されていることを知らずに汎用的に扱ったりする場合に発生します。レジスタがclobberされると、プログラムの動作が予測不能になったり、クラッシュしたりする原因となります。
Goランタイムのm
とg
Go言語は、独自の軽量な実行環境である「Goランタイム」を持っています。このランタイムは、ゴルーチン(Goの軽量スレッド)のスケジューリング、メモリ管理、ガベージコレクションなどを担当します。
g
(goroutine): 現在実行中のゴルーチンのコンテキストを指すポインタ。各ゴルーチンは独自のスタックを持ち、g
ポインタはそのゴルーチンのスタックやその他の状態情報にアクセスするために使用されます。m
(machine): 現在のOSスレッド(マシン)に紐づくGoのスケジューラコンテキストを指すポインタ。m
は、現在のOSスレッドがどのゴルーチンを実行しているか、ネットワークポーラーの状態、スケジューラの統計情報などを管理します。
ARMアーキテクチャでは、Goランタイムはパフォーマンス上の理由から、これらのm
とg
のポインタを特定のハードウェアレジスタに保持することがあります。これにより、頻繁にアクセスされるこれらのポインタへのアクセスが高速化されます。このコミットの時点では、R9がm
、R10がg
に割り当てられていました。
cmd/5a
とGoのアセンブラ
cmd/5a
は、Goのツールチェーンの一部であり、ARMアーキテクチャ向けのアセンブラです。Goは、C言語のような外部アセンブラではなく、独自のPlan 9アセンブラを使用します。このアセンブラは、Goのコンパイラと密接に連携し、Goのランタイムや標準ライブラリの一部をアセンブリ言語で記述するために使用されます。
cmd/dist
cmd/dist
は、Goのビルドシステムの一部であり、GoのソースコードからGoツールチェーン全体をビルドするために使用されます。これには、コンパイラ、アセンブラ、リンカ、標準ライブラリなどが含まれます。buildruntime.c
は、ランタイムのビルドプロセスに関連する設定や定義を生成する役割を担っています。
技術的詳細
このコミットの核心は、GoのARMアセンブラがR9とR10を汎用レジスタとして扱うことを停止し、代わりにm
とg
というシンボリックな名前を導入した点にあります。
-
cmd/5a/lex.c
の変更:lex.c
は、Goのアセンブラの字句解析器の一部です。ここでは、アセンブリコード内で認識されるレジスタ名が定義されています。- 変更前は、
"R9"
と"R10"
がそれぞれレジスタ番号9と10にマッピングされていました。 - 変更後は、
"R9"
と"R10"
のエントリが削除され、代わりに"m"
がレジスタ番号9に、"g"
がレジスタ番号10にマッピングされました。コメントで「avoid unintentionally clobber m/g using R9/R10」と明記されており、意図しないm
とg
の破壊を防ぐ目的が強調されています。
-
src/cmd/dist/buildruntime.c
の変更:- このファイルは、ランタイムのビルド時に使用されるマクロ定義を生成します。
- 変更前は、ARMアーキテクチャ向けに
#define g R10
と#define m R9
というマクロ定義が含まれていました。これは、Cコード内でg
やm
というシンボルがR10やR9として扱われるようにするためのものでした。 - このコミットでは、これらの
#define
行が削除されました。これは、アセンブラレベルでm
とg
が直接レジスタとして扱われるようになったため、Cコード側での明示的なマッピングが不要になったことを意味します。
-
src/pkg/runtime/cgo/asm_arm.s
の変更:- このファイルは、Cgo(GoとCの相互運用)に関連するARMアセンブリコードを含んでいます。
crosscall2
という関数内で、レジスタの保存/復元を行うMOVM.WP
およびMOVM.IAW
命令が使用されています。- 変更前は、これらの命令で
R9, R10
が直接指定されていました。 - 変更後は、
R9, R10
の代わりにm, g
が使用されています。これは、アセンブラがm
とg
を認識するようになったため、よりセマンティックな記述が可能になったことを示しています。
-
src/pkg/runtime/vlop_arm.s
の変更:- このファイルは、ARMアーキテクチャ向けの低レベルなランタイム操作(特に除算などの数値演算)を実装したアセンブリコードを含んでいます。
- このファイル内では、
m
というシンボルが一時レジスタとして使用されていました。しかし、Goランタイムのm
ポインタと名前が衝突し、混乱を招く可能性がありました。 - 変更点として、
m = 3
という定義がM = 3
に変更され、それに伴いファイル内のすべてのR(m)
参照がR(M)
に変更されました。これは、Goランタイムのm
と区別するために、ローカルな一時レジスタのシンボル名を大文字のM
に変更したものです。これにより、コードの可読性と保守性が向上し、意図しない名前の衝突を防いでいます。
これらの変更は、GoのARMアセンブラとランタイムの間の契約を明確にし、m
とg
がGoランタイムにとって特別な意味を持つレジスタであることを強制することで、将来的なバグの発生を防ぐことを目的としています。
コアとなるコードの変更箇所
doc/go1.2.txt
--- a/doc/go1.2.txt
+++ b/doc/go1.2.txt
@@ -8,5 +8,7 @@ Please keep the descriptions to a single line, starting with the
package or cmd/xxx directory name, and ending in a CL number.
Please keep the list sorted (as in sort.Strings of the lines).
+cmd/5a: removed support for R9/R10 (use m/g instead) (CL 9840043).
+
fmt: indexed access to arguments in Printf etc. (CL 9680043).
io: Copy prioritizes WriterTo over ReaderFrom (CL 9462044).
src/cmd/5a/lex.c
--- a/src/cmd/5a/lex.c
+++ b/src/cmd/5a/lex.c
@@ -191,8 +191,8 @@ struct
"R6", LREG, 6,
"R7", LREG, 7,
"R8", LREG, 8,
- "R9", LREG, 9,
- "R10", LREG, 10,
+ "m", LREG, 9, // avoid unintentionally clobber m/g using R9/R10
+ "g", LREG, 10,
"R11", LREG, 11,
"R12", LREG, 12,
"R13", LREG, 13,
src/cmd/dist/buildruntime.c
--- a/src/cmd/dist/buildruntime.c
+++ b/src/cmd/dist/buildruntime.c
@@ -174,8 +174,6 @@ static struct {
},
{"arm", "",
- "#define g R10\n"
- "#define m R9\n"
"#define LR R14\n"
},
};
src/pkg/runtime/cgo/asm_arm.s
--- a/src/pkg/runtime/cgo/asm_arm.s
+++ b/src/pkg/runtime/cgo/asm_arm.s
@@ -15,9 +15,9 @@ TEXT crosscall2(SB),7,$-4
* Additionally, cgo_tls_set_gm will clobber R0, so we need to save R0
* nevertheless.
*/
- MOVM.WP [R0, R1, R2, R4, R5, R6, R7, R8, R9, R10, R11, R12, R14], (R13)
+ MOVM.WP [R0, R1, R2, R4, R5, R6, R7, R8, m, g, R11, R12, R14], (R13)
MOVW _cgo_load_gm(SB), R0
BL (R0)
MOVW PC, R14
MOVW 0(R13), PC
- MOVM.IAW (R13), [R0, R1, R2, R4, R5, R6, R7, R8, R9, R10, R11, R12, PC]
+ MOVM.IAW (R13), [R0, R1, R2, R4, R5, R6, R7, R8, m, g, R11, R12, PC]
src/pkg/runtime/vlop_arm.s
--- a/src/pkg/runtime/vlop_arm.s
+++ b/src/pkg/runtime/vlop_arm.s
@@ -70,7 +70,7 @@ TEXT _sfloat(SB), 7, $64 // 4 arg + 14*4 saved regs + cpsr
q = 0 // input d, output q
r = 1 // input n, output r
s = 2 // three temporary variables
-m = 3
+M = 3
a = 11
// Please be careful when changing this, it is pretty fragile:
// 1, don't use unconditional branch as the linker is free to reorder the blocks;
@@ -83,31 +83,31 @@ TEXT udiv<>(SB),7,$-4
begin:
SUB.S $7, R(s)
- RSB $0, R(q), R(m) // m = -q
+ RSB $0, R(q), R(M) // M = -q
MOVW.PL R(a)<<R(s), R(q)
// 1st Newton iteration
- MUL.PL\tR(m), R(q), R(a) // a = -q*d
+ MUL.PL\tR(M), R(q), R(a) // a = -q*d
BMI udiv_by_large_d
MULAWT\tR(a), R(q), R(q), R(q) // q approx q-(q*q*d>>32)
- TEQ \tR(m)->1, R(m) // check for d=0 or d=1
+ TEQ \tR(M)->1, R(M) // check for d=0 or d=1
// 2nd Newton iteration
- MUL.NE\tR(m), R(q), R(a)
+ MUL.NE\tR(M), R(q), R(a)
MOVW.NE\t$0, R(s)
MULAL.NE R(q), R(a), (R(q),R(s))\
BEQ \tudiv_by_0_or_1
// q now accurate enough for a remainder r, 0<=r<3*d
MULLU\tR(q), R(r), (R(q),R(s)) // q = (r * q) >> 32
- ADD \tR(m), R(r), R(r) // r = n - d
- MULA\tR(m), R(q), R(r), R(r) // r = n - (q+1)*d
+ ADD \tR(M), R(r), R(r) // r = n - d
+ MULA\tR(M), R(q), R(r), R(r) // r = n - (q+1)*d
// since 0 <= n-q*d < 3*d; thus -d <= r < 2*d
- CMN \tR(m), R(r) // t = r-d
- SUB.CS\tR(m), R(r), R(r) // if (t<-d || t>=0) r=r+d
+ CMN \tR(M), R(r) // t = r-d
+ SUB.CS\tR(M), R(r), R(r) // if (t<-d || t>=0) r=r+d
ADD.CC\t$1, R(q)
- ADD.PL\tR(m)<<1, R(r)
+ ADD.PL\tR(M)<<1, R(r)
ADD.PL\t$2, R(q)
// return, can't use RET here or fast_udiv_tab will be dropped during linking
@@ -119,14 +119,14 @@ udiv_by_large_d:
RSB $0, R(s), R(s)
MOVW\tR(a)>>R(s), R(q)
MULLU\tR(q), R(r), (R(q),R(s))
- MULA\tR(m), R(q), R(r), R(r)
+ MULA\tR(M), R(q), R(r), R(r)
// q now accurate enough for a remainder r, 0<=r<4*d
- CMN \tR(r)>>1, R(m) // if(r/2 >= d)
- ADD.CS\tR(m)<<1, R(r)
+ CMN \tR(r)>>1, R(M) // if(r/2 >= d)
+ ADD.CS\tR(M)<<1, R(r)
ADD.CS\t$2, R(q)
- CMN \tR(r), R(m)
- ADD.CS\tR(m), R(r)
+ CMN \tR(r), R(M)
+ ADD.CS\tR(M), R(r)
ADD.CS\t$1, R(q)
// return, can't use RET here or fast_udiv_tab will be dropped during linking
@@ -170,7 +170,7 @@ TEXT _divu(SB), 7, $16
MOVW\tR(q), 4(R13)\
MOVW\tR(r), 8(R13)\
MOVW\tR(s), 12(R13)\
- MOVW\tR(m), 16(R13)\
+ MOVW\tR(M), 16(R13)\
MOVW\tR(TMP), R(r) /* numerator */
MOVW\t0(FP), R(q) /* denominator */
@@ -179,14 +179,14 @@ TEXT _divu(SB), 7, $16
MOVW\t4(R13), R(q)\
MOVW\t8(R13), R(r)\
MOVW\t12(R13), R(s)\
- MOVW\t16(R13), R(m)\
+ MOVW\t16(R13), R(M)\
RET\
TEXT _modu(SB), 7, $16
MOVW\tR(q), 4(R13)\
MOVW\tR(r), 8(R13)\
MOVW\tR(s), 12(R13)\
- MOVW\tR(m), 16(R13)\
+ MOVW\tR(M), 16(R13)\
MOVW\tR(TMP), R(r) /* numerator */
MOVW\t0(FP), R(q) /* denominator */
@@ -195,14 +195,14 @@ TEXT _modu(SB), 7, $16
MOVW\t4(R13), R(q)\
MOVW\t8(R13), R(r)\
MOVW\t12(R13), R(s)\
- MOVW\t16(R13), R(m)\
+ MOVW\t16(R13), R(M)\
RET\
TEXT _div(SB),7,$16
MOVW\tR(q), 4(R13)\
MOVW\tR(r), 8(R13)\
MOVW\tR(s), 12(R13)\
- MOVW\tR(m), 16(R13)\
+ MOVW\tR(M), 16(R13)\
MOVW\tR(TMP), R(r) /* numerator */
MOVW\t0(FP), R(q) /* denominator */
CMP \t$0, R(r)\
@@ -228,5 +228,5 @@ out:
MOVW\t4(R13), R(q)\
MOVW\t8(R13), R(r)\
MOVW\t12(R13), R(s)\
- MOVW\t16(R13), R(m)\
+ MOVW\t16(R13), R(M)\
RET\
コアとなるコードの解説
このコミットにおける最も重要な変更は、GoのARMアセンブラがレジスタR9とR10を直接参照する代わりに、m
とg
というシンボリックな名前を使用するようにした点です。
-
src/cmd/5a/lex.c
: このファイルはアセンブラの字句解析器の一部であり、アセンブリコード内で使用されるシンボル(レジスタ名など)を定義しています。以前は"R9"
と"R10"
が直接レジスタ番号9と10にマッピングされていましたが、この変更により、"m"
がレジスタ9に、"g"
がレジスタ10にマッピングされるようになりました。これにより、アセンブリコードを書く開発者は、これらのレジスタがGoランタイムのm
とg
ポインタのために予約されていることを明確に認識し、誤って上書きする(clobberする)ことを防ぐことができます。コメント// avoid unintentionally clobber m/g using R9/R10
がその意図を明確に示しています。 -
src/cmd/dist/buildruntime.c
: このファイルは、Goランタイムのビルドプロセスで、Cコードからアセンブリコードへのマッピングを定義していました。以前は#define g R10
と#define m R9
というマクロ定義がありましたが、アセンブラ自体がm
とg
を認識するようになったため、これらのマクロは不要となり削除されました。これは、Goのビルドシステムとアセンブラの間の連携がより密接になったことを示しています。 -
src/pkg/runtime/cgo/asm_arm.s
: Cgoのランタイムアセンブリコードでは、レジスタの保存と復元を行うMOVM.WP
とMOVM.IAW
命令が使用されています。以前はR9, R10
と直接レジスタ番号で指定されていましたが、この変更によりm, g
というシンボリックな名前を使用するようになりました。これにより、コードの意図がより明確になり、Goランタイムの内部構造との整合性が保たれます。 -
src/pkg/runtime/vlop_arm.s
: このファイルは、ARMアーキテクチャでの低レベルな数値演算(特に除算)のアセンブリ実装を含んでいます。このファイル内で一時変数としてm
というシンボルが使用されていましたが、Goランタイムのm
ポインタと名前が衝突する可能性がありました。このコミットでは、この一時変数の名前をm
からM
(大文字)に変更し、それに伴い関連するすべての参照もR(m)
からR(M)
に修正されました。これは、名前の衝突を避け、コードの明確性を高めるための重要な変更です。
これらの変更は、GoのARMポートにおけるランタイムの堅牢性を高め、開発者が意図しないレジスタの誤用によって引き起こされるデバッグ困難なバグを回避できるようにすることを目的としています。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- GoのARMアセンブリ: https://go.dev/doc/asm
- Goのランタイムスケジューラに関する解説(一般的な情報源): Go scheduler: M, P, G - The Go Programming Language Blog (Go公式ブログや関連技術ブログで検索可能)
参考にした情報源リンク
- Goのコミット履歴: https://github.com/golang/go/commits/master
- GoのIssueトラッカー (CL 6300043, CL 6305100などの詳細情報): https://go.dev/issue (具体的なCL番号で検索)
- ARMアーキテクチャリファレンスマニュアル (レジスタに関する詳細情報): ARM社の公式ドキュメント
- GoのPlan 9アセンブラに関する情報: Goの公式ドキュメントや関連する技術記事
- Goのランタイムに関する技術ブログや解説記事 (例: "Go's scheduler: M, P, G" などで検索)
- Goのソースコード (特に
src/cmd/5a
,src/cmd/dist
,src/pkg/runtime
ディレクトリ)