[インデックス 18346] ファイルの概要
このコミットは、Go言語のARMアーキテクチャにおけるCgo(C言語との相互運用機能)のTLS(Thread Local Storage)実装に関するバグ修正です。特に、liblink
リンカとGoランタイムがTLSベースポインタとg
(goroutine)およびm
(machine)構造体へのオフセットを扱う方法に存在した、複雑で誤解を招くような挙動を根本的に改善することを目的としています。以前の実装では、内部リンカと外部リンカでMRC
命令の解釈が異なったり、liblink
がWORD
命令を誤って書き換えたりする問題がありました。このコミットでは、リンカが提供する特別なシンボルruntime.tlsgm
を導入し、R_ARM_TLS_LE32
リロケーションタイプを利用することで、より堅牢で理解しやすいTLSアクセス機構を確立しています。
コミット
commit b377c9c6a9b720d0897d298652bebd3887ceeb46
Author: Russ Cox <rsc@golang.org>
Date: Thu Jan 23 22:51:39 2014 -0500
liblink, runtime: fix cgo on arm
The addition of TLS to ARM rewrote the MRC instruction
differently depending on whether we were using internal
or external linking mode. That's clearly not okay, since we
don't know that during compilation, which is when we now
generate the code. Also, because the change did not introduce
a real MRC instruction but instead just macro-expanded it
in the assembler, liblink is rewriting a WORD instruction that
may actually be looking for that specific constant, which would
lead to very unexpected results. It was also using one value
that happened to be 8 where a different value that also
happened to be 8 belonged. So the code was correct for those
values but not correct in general, and very confusing.
Throw it all away.
Replace with the following. There is a linker-provided symbol
runtime.tlsgm with a value (address) set to the offset from the
hardware-provided TLS base register to the g and m storage.
Any reference to that name emits an appropriate TLS relocation
to be resolved by either the internal linker or the external linker,
depending on the link mode. The relocation has exactly the
semantics of the R_ARM_TLS_LE32 relocation, which is what
the external linker provides.
This symbol is only used in two routines, runtime.load_gm and
runtime.save_gm. In both cases it is now used like this:
MRC 15, 0, R0, C13, C0, 3 // fetch TLS base pointer
MOVW $runtime·tlsgm(SB), R2
ADD R2, R0 // now R0 points at thread-local g+m storage
It is likely that this change breaks the generation of shared libraries
on ARM, because the MOVW needs to be rewritten to use the global
offset table and a different relocation type. But let's get the supported
functionality working again before we worry about unsupported
functionality.
LGTM=dave, iant
R=iant, dave
CC=golang-codereviews
https://golang.org/cl/56120043
---\n src/cmd/ld/data.c | 25 +++++++++++-----\n src/cmd/ld/lib.c | 5 +---\n src/liblink/asm5.c | 10 ++++++-\n src/liblink/obj5.c | 74 -----------------------------------------------\n src/pkg/runtime/asm_arm.s | 35 ++++++++++++++--------\n 5 files changed, 51 insertions(+), 98 deletions(-)\n
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b377c9c6a9b720d0897d298652bebd3887ceeb46
元コミット内容
このコミットは、Go言語のARMアーキテクチャにおけるCgo(C言語との相互運用)機能のTLS(Thread Local Storage)に関する問題を修正するものです。
以前のTLS実装では、MRC
命令(ARMアーキテクチャにおけるコプロセッサレジスタ操作命令)の扱いが、内部リンカと外部リンカで異なっていました。これはコンパイル時にリンカの種類が不明であるため、問題を引き起こしていました。また、この変更は実際のMRC
命令を導入するのではなく、アセンブラでマクロ展開される形であったため、liblink
がWORD
命令を書き換える際に、本来その定数を期待しているコードに対して予期せぬ結果をもたらす可能性がありました。さらに、特定のオフセット値が偶然にも8
であったために正しく動作していたものの、汎用的な解決策ではなく、非常に混乱を招くものでした。
このコミットでは、これらの問題を根本的に解決するために、既存の複雑な実装をすべて破棄し、新しいアプローチを導入しています。新しいアプローチでは、リンカが提供するruntime.tlsgm
というシンボルを使用します。このシンボルは、ハードウェアが提供するTLSベースレジスタからg
(goroutine構造体)とm
(machine構造体)のストレージへのオフセット値(アドレス)を持ちます。このシンボルへの参照は、リンカモード(内部または外部)に応じて適切なTLSリロケーション(R_ARM_TLS_LE32
リロケーションと同じセマンティクスを持つ)を生成します。
runtime.tlsgm
シンボルは、runtime.load_gm
とruntime.save_gm
の2つのルーチンでのみ使用されます。これらのルーチンでは、まずMRC
命令でTLSベースポインタを取得し、次にruntime.tlsgm
のオフセットをMOVW
でレジスタにロードし、最後にADD
命令でTLSベースポインタにオフセットを加算することで、スレッドローカルなg
とm
のストレージを指すようにします。
この変更は、ARM上の共有ライブラリの生成を一時的に壊す可能性があると述べられています。これは、MOVW
命令がグローバルオフセットテーブル(GOT)と異なるリロケーションタイプを使用するように書き換えられる必要があるためです。しかし、このコミットの目的は、まずサポートされている機能が再び動作するようにすることであり、サポートされていない機能(共有ライブラリ)については後で対処する方針が示されています。
変更の背景
このコミットの背景には、Go言語のARMアーキテクチャにおけるCgoとTLSの複雑な相互作用に起因する複数の問題がありました。
- リンカモードによる挙動の不一致: Goのツールチェインは、内部リンカと外部リンカ(通常はGCCなど)の両方を使用できます。TLSをARMに追加した際、
MRC
命令(ARMのコプロセッサレジスタ操作命令で、TLSベースポインタの取得によく使われる)の解釈やコード生成が、どちらのリンカを使用するかによって異なっていました。これは、コンパイル時には最終的にどちらのリンカが使用されるか分からないため、非常に不安定な挙動を引き起こしていました。 liblink
によるWORD
命令の誤った書き換え: 以前の実装では、MRC
命令がアセンブラでマクロ展開される形で導入されていました。これにより、liblink
(Goの内部リンカ)が、本来特定の定数を期待している可能性のあるWORD
命令を、TLS関連のコードとして誤って書き換えてしまう問題が発生していました。これは、予期せぬバグやクラッシュにつながる可能性がありました。- マジックナンバーと混乱: コード内には、TLSオフセットに関連する値として
8
という定数が使われていましたが、これは偶然にも正しい値であっただけで、その意味が不明瞭であり、汎用的な解決策ではありませんでした。このようなマジックナンバーは、コードの可読性を低下させ、将来のメンテナンスを困難にします。 - 共有ライブラリ生成への影響: TLSの扱いは、特に共有ライブラリ(ダイナミックリンクライブラリ)の生成において複雑になります。以前の実装は、共有ライブラリの生成を考慮していなかったか、不適切に扱っていた可能性があります。このコミットの変更も、一時的に共有ライブラリの生成に影響を与える可能性が指摘されていますが、まずは基本的なCgoとTLSの機能が正しく動作することを優先しています。
これらの問題は、GoプログラムがCgoを介してCライブラリと連携する際に、スレッドローカルなデータ(特にGoランタイムのg
とm
ポインタ)のアクセスに不整合やエラーを引き起こす可能性がありました。このコミットは、これらの根本的な問題を解決し、ARM上でのCgoの安定性と信頼性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の技術的な概念を把握しておく必要があります。
- Cgo: Go言語とC言語のコードを相互に呼び出すためのGoの機能です。Cgoを使用すると、Goプログラムから既存のCライブラリを利用したり、CコードからGo関数を呼び出したりできます。Cgoは、GoランタイムとCランタイム(通常はglibcなどの標準Cライブラリ)の間で、スレッド、メモリ、レジスタなどのコンテキスト切り替えを伴うため、特に低レベルのアーキテクチャ固有の考慮事項が必要になります。
- ARMアーキテクチャ: Advanced RISC Machineの略で、モバイルデバイスや組み込みシステムで広く使用されているCPUアーキテクチャです。ARMプロセッサは、RISC(Reduced Instruction Set Computer)の原則に基づいて設計されており、効率的な電力消費と高い性能を両立しています。
- レジスタ: ARMプロセッサには汎用レジスタ(R0-R12)、スタックポインタ(SP, R13)、リンクレジスタ(LR, R14)、プログラムカウンタ(PC, R15)などがあります。
MRC
命令: "Move from Coprocessor Register"の略で、ARMプロセッサがコプロセッサのレジスタからデータを汎用レジスタに転送するために使用する命令です。TLSベースポインタの取得によく使われるCP15
コプロセッサのC13
レジスタ(Thread ID Register)からの読み出しは、MRC p15, 0, Rd, c13, c0, 3
のような形式で行われます。
- TLS (Thread Local Storage): スレッドローカルストレージは、各スレッドが独自のデータコピーを持つことを可能にするメモリ領域です。グローバル変数とは異なり、TLSに格納されたデータは、同じプロセス内の他のスレッドからは直接アクセスできません。Goランタイムでは、現在のgoroutine(
g
)と現在のOSスレッド(m
)のポインタをTLSに格納することが一般的です。これにより、Cgo呼び出しなどでOSスレッドが切り替わった際に、Goランタイムが正しいg
とm
のコンテキストを迅速に取得できます。 - Goの
g
とm
:g
(goroutine): Go言語の軽量スレッドです。Goランタイムによってスケジューリングされ、OSスレッド上で実行されます。各g
は独自のスタックを持ちます。m
(machine): OSスレッドを表します。Goランタイムは、複数のg
を少数のm
上で多重化して実行します。Cgo呼び出しを行う際など、Goランタイムは現在のg
とm
のポインタをTLSに保存し、Cコードの実行後にそれらを復元する必要があります。
- リンカ: コンパイラによって生成されたオブジェクトファイル(機械語コードとデータ)を結合し、実行可能ファイルやライブラリを生成するプログラムです。
- 内部リンカ (
liblink
): Go言語のツールチェインに組み込まれている独自のリンカです。Goの特殊なランタイム要件やクロスコンパイルの容易さを実現するために使用されます。 - 外部リンカ (e.g., GCC): システムにインストールされている標準的なリンカです。Cgoを使用する場合など、GoのコードがCライブラリとリンクされる際に使用されることがあります。
- 内部リンカ (
- リロケーション (Relocation): リンカがオブジェクトファイルを結合する際に、コード内のアドレス参照を修正するプロセスです。コンパイル時には、関数やデータの最終的なメモリ上のアドレスは不明なため、プレースホルダーが使用されます。リンカは、これらのプレースホルダーを実際のメモリ上のアドレスに置き換えます。
R_ARM_TLS_LE32
: ARMアーキテクチャにおけるTLS関連のリロケーションタイプの一つです。これは、TLS変数のオフセットを、TLSベースポインタからの相対アドレスとして解決するために使用されます。通常、TLSベースポインタは特定のレジスタ(ARMではTPIDRURW
などのTLSレジスタ)に格納されており、このリロケーションは、そのレジスタからのオフセットを計算して、TLSデータにアクセスするための命令を生成します。R_ARM_TLS_IE32
: ARMアーキテクチャにおけるTLS関連のリロケーションタイプで、特に共有ライブラリ(PIC: Position-Independent Code)で使用されます。これは、TLS変数がGOT(Global Offset Table)を介してアクセスされる場合に用いられます。
- GOT (Global Offset Table): 共有ライブラリにおいて、グローバル変数や関数のアドレスを間接的に参照するために使用されるテーブルです。PICでは、コードがメモリ上のどこにロードされても正しく動作するように、絶対アドレスではなく相対アドレスを使用します。GOTは、実行時に実際の絶対アドレスが解決され、そこに格納されます。
技術的詳細
このコミットの技術的詳細を理解するには、以前のTLS実装の問題点と、新しいruntime.tlsgm
シンボルに基づくアプローチの仕組みを深く掘り下げる必要があります。
以前のTLS実装の問題点
コミットメッセージが指摘するように、以前のARM TLS実装はいくつかの深刻な問題を抱えていました。
MRC
命令のリンカ依存の挙動:MRC 15, 0, R0, C13, C0, 3
という命令は、ARMのCP15コプロセッサ(システム制御コプロセッサ)のC13レジスタ(Thread ID Register)から値をR0レジスタに読み込むものです。このレジスタは通常、TLSベースポインタを保持しています。- 問題は、Goの内部リンカ(
liblink
)と外部リンカ(例えばGCCのリンカ)が、この命令の後のコード生成やリロケーションの処理を異なる方法で行っていたことです。コンパイル時にはどちらのリンカが使われるか不明なため、生成されるコードがリンカによって変わるという不安定な状態でした。
liblink
によるWORD
命令の誤った書き換え:- 以前の実装では、TLS関連のコードがアセンブラでマクロ展開され、
MRC
命令の後に特定のWORD
命令(データとして埋め込まれた32ビット値)が続くことがありました。 liblink
は、このWORD
命令をTLSオフセットの調整のために書き換えるロジックを持っていました。しかし、このWORD
命令がTLSオフセットとは無関係な、本来の定数として使用されるべき場所であった場合、liblink
の書き換えによってその定数が破壊され、予期せぬバグやクラッシュを引き起こす可能性がありました。
- 以前の実装では、TLS関連のコードがアセンブラでマクロ展開され、
- マジックナンバーと混乱:
8
というオフセット値がコード内で使われていましたが、これはELF ARMにおけるスレッドポインタがスレッドローカルデータブロックの開始から8バイト手前にあるという「基本的な定数」と、Go自身のTLSストレージがたまたま8バイトを占めるという二つの異なる意味を持っていました。この偶然の一致が、コードを混乱させ、汎用性を欠くものにしていました。
新しいruntime.tlsgm
アプローチ
これらの問題を解決するため、コミットは「すべてを捨て去る」という大胆なアプローチを取り、runtime.tlsgm
という新しいリンカ提供シンボルを導入しました。
runtime.tlsgm
シンボルの導入:runtime.tlsgm
は、Goランタイムのg
とm
のストレージが、ハードウェアが提供するTLSベースレジスタからどれだけオフセットしているかを示す値(アドレス)を持つ特別なシンボルです。- このシンボルは、
src/cmd/ld/data.c
のdodata
関数内でSTLSBSS
タイプとして定義され、そのvalue
が0
に設定されます。これは、TLSベースポインタからの相対オフセットであることを示唆しています。 src/cmd/ld/lib.c
では、gmsym
(runtime.tlsgm
に対応)のreachable
フラグが常に1
に設定され、リンカがこのシンボルを常に考慮するようにします。
R_ARM_TLS_LE32
リロケーションの利用:runtime.tlsgm
への参照は、src/liblink/asm5.c
のasmout
関数内で、D_TLS
タイプのリロケーションとして処理されます。- このリロケーションは、外部リンカが提供する
R_ARM_TLS_LE32
リロケーションと全く同じセマンティクスを持つように設計されています。R_ARM_TLS_LE32
は、TLS変数のオフセットをTLSベースポインタからの相対アドレスとして解決するための標準的なARMリロケーションタイプです。 - これにより、内部リンカと外部リンカの両方で、
runtime.tlsgm
への参照が適切に解決され、リンカモードに依存しない一貫した挙動が保証されます。 src/cmd/ld/data.c
のrelocsym
関数では、ELF ARMの場合、D_TLS
リロケーションに対して、TLSベースポインタからのオフセットに8バイトを加算するロジックが追加されています。これは、ELF ARMのTLSポインタがデータブロックの開始から8バイト手前にあるという仕様に対応するためです。
runtime.load_gm
とruntime.save_gm
の変更:src/pkg/runtime/asm_arm.s
内のruntime·save_gm
とruntime·load_gm
ルーチンが、新しいアプローチを使用するように変更されました。- これらのルーチンは、まず
MRC
命令でTLSベースポインタをR0レジスタに取得します。 - 次に、
MOVW $runtime·tlsgm(SB), R11
命令でruntime.tlsgm
シンボルの値(TLSベースポインタからのオフセット)をR11レジスタにロードします。 - 最後に、
ADD R11, R0
命令で、R0(TLSベースポインタ)にR11(オフセット)を加算し、R0がg
とm
のストレージを直接指すようにします。 - これにより、以前の
liblink
による「魔法のような」オフセット調整が不要になり、コードがより明示的で理解しやすくなりました。
src/liblink/obj5.c
からの古いロジックの削除:- このファイルからは、以前の
AWORD
命令の書き換えや、MRC
命令の後の複雑なTLSオフセット調整ロジックが大量に削除されています。これは、新しいruntime.tlsgm
アプローチがこれらの古い、問題のある実装を完全に置き換えることを意味します。
- このファイルからは、以前の
共有ライブラリへの影響
コミットメッセージは、この変更がARM上の共有ライブラリの生成を「壊す可能性が高い」と明記しています。これは、共有ライブラリでは位置独立コード(PIC)が必要であり、MOVW $runtime·tlsgm(SB), R11
のような命令が、直接的なアドレスではなく、GOT(Global Offset Table)を介して解決される必要があるためです。R_ARM_TLS_IE32
のような異なるリロケーションタイプが必要になる可能性があります。しかし、このコミットの優先順位は、まずサポートされているCgo機能が正しく動作するようにすることであり、共有ライブラリのサポートは後回しにされています。
この修正により、ARMアーキテクチャにおけるGoのCgo機能は、より堅牢で予測可能なTLSアクセスを実現し、将来的な拡張やデバッグが容易になりました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。
-
src/pkg/runtime/asm_arm.s
:runtime·save_gm
とruntime·load_gm
という2つのアセンブリルーチンが大幅に変更されています。- 以前は
liblink
による「魔法のような」オフセット調整に依存していましたが、新しいコードではruntime·tlsgm
シンボルを明示的に使用してg
とm
のTLSストレージへのオフセットを計算しています。
--- a/src/pkg/runtime/asm_arm.s +++ b/src/pkg/runtime/asm_arm.s @@ -627,22 +627,33 @@ _next: // Note: all three functions will clobber R0, and the last // two can be called from 5c ABI code. +// save_gm saves the g and m registers into pthread-provided +// thread-local memory, so that we can call externally compiled +// ARM code that will overwrite those registers. +// NOTE: runtime.gogo assumes that R1 is preserved by this function. TEXT runtime·save_gm(SB),NOSPLIT,$0 - // NOTE: Liblink adds some instructions following the MRC - // to adjust R0 so that 8(R0) and 12(R0) are the TLS copies of - // the g and m registers. It's a bit too magical for its own good. - MRC 15, 0, R0, C13, C0, 3 // Fetch TLS register - MOVW g, 8(R0) - MOVW m, 12(R0) + MRC 15, 0, R0, C13, C0, 3 // fetch TLS base pointer + // $runtime.tlsgm(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 + ADD R11, R0 + MOVW g, 0(R0) + MOVW m, 4(R0) RET +// load_gm loads the g and m registers 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 - // NOTE: Liblink adds some instructions following the MRC - // to adjust R0 so that 8(R0) and 12(R0) are the TLS copies of - // the g and m registers. It's a bit too magical for its own good. - MRC 15, 0, R0, C13, C0, 3 // Fetch TLS register - MOVW 8(R0), g - MOVW 12(R0), m + MRC 15, 0, R0, C13, C0, 3 // fetch TLS base pointer + // $runtime.tlsgm(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 + ADD R11, R0 + MOVW 0(R0), g + MOVW 4(R0), m RET
-
src/liblink/obj5.c
:- このファイルからは、以前のTLSレジスタフェッチ(
MRC
命令の書き換え)と、それに続くMOVW
、ADD
、SUB
命令による複雑なTLSオフセット調整ロジックが大量に削除されています。これは、新しいアプローチがこれらの古い、問題のある実装を完全に置き換えたことを示しています。
--- a/src/liblink/obj5.c +++ b/src/liblink/obj5.c @@ -349,78 +347,6 @@ addstacksplit(Link *ctxt, LSym *cursym) } } break; - case AWORD: - // Rewrite TLS register fetch: MRC 15, 0, <reg>, C13, C0, 3 - if((p->to.offset & 0xffff0fff) == 0xee1d0f70) { - if(ctxt->headtype == Hopenbsd) { - p->as = ARET; - break; - } - if(ctxt->goarm < 7) { - // BL runtime.read_tls_fallback(SB) - p->as = ABL; - p->to.type = D_BRANCH; - p->to.sym = tlsfallback; - p->to.offset = 0; - cursym->text->mark &= ~LEAF; - } - // runtime.tlsgm is relocated with R_ARM_TLS_LE32 - // and $runtime.tlsgm will contain the TLS offset. - // - // MOV $runtime.tlsgm+ctxt->tlsoffset(SB), REGTMP - // ADD REGTMP, <reg> - // - // In shared mode, runtime.tlsgm is relocated with - // R_ARM_TLS_IE32 and runtime.tlsgm(SB) will point - // to the GOT entry containing the TLS offset. - // - // MOV runtime.tlsgm(SB), REGTMP - // ADD REGTMP, <reg> - // SUB -ctxt->tlsoffset, <reg> - // - // The SUB compensates for ctxt->tlsoffset - // used in runtime.save_gm and runtime.load_gm. - q = p; - p = appendp(ctxt, p); - p->as = AMOVW; - p->scond = C_SCOND_NONE; - p->reg = NREG; - if(ctxt->flag_shared) { - p->from.type = D_OREG; - p->from.offset = 0; - } else { - p->from.type = D_CONST; - p->from.offset = ctxt->tlsoffset; - } - p->from.sym = ctxt->gmsym; - p->from.name = D_EXTERN; - p->to.type = D_REG; - p->to.reg = REGTMP; - p->to.offset = 0; - - p = appendp(ctxt, p); - p->as = AADD; - p->scond = C_SCOND_NONE; - p->reg = NREG; - p->from.type = D_REG; - p->from.reg = REGTMP; - p->to.type = D_REG; - p->to.reg = (q->to.offset & 0xf000) >> 12; - p->to.offset = 0; - - if(ctxt->flag_shared) { - p = appendp(ctxt, p); - p->as = ASUB; - p->scond = C_SCOND_NONE; - p->reg = NREG; - p->from.type = D_CONST; - p->from.offset = -ctxt->tlsoffset; - p->to.type = D_REG; - p->to.reg = (q->to.offset & 0xf000) >> 12; - p->to.offset = 0; - } - break; - } } q = p; }
- このファイルからは、以前のTLSレジスタフェッチ(
-
src/cmd/ld/data.c
:relocsym
関数内のD_TLS
リロケーションの処理に、ELF ARMの場合のオフセット調整(o = 8 + r->sym->value;
)が追加されています。dodata
関数内で、STLSBSS
シンボル(特にruntime.tlsgm
)のvalue
が0
に設定されるロジックが追加されています。
-
src/liblink/asm5.c
:asmout
関数内で、runtime.tlsgm
シンボルに対するD_TLS
リロケーションの生成ロジックが追加されています。これにより、runtime.tlsgm
への参照が適切にTLSリロケーションとして扱われるようになります。
-
src/cmd/ld/lib.c
:loadlib
関数内で、gmsym
(runtime.tlsgm
に対応)のreachable
フラグが常に1
に設定されるようになりました。
これらの変更は、GoのARM TLS実装の根本的な再設計を反映しており、特にアセンブリコードとリンカの挙動に大きな影響を与えています。
コアとなるコードの解説
このコミットの核となる変更は、src/pkg/runtime/asm_arm.s
内のruntime·save_gm
とruntime·load_gm
アセンブリルーチンにあります。これらのルーチンは、Cgo呼び出しの前後でGoランタイムのg
(goroutine)とm
(machine)ポインタをスレッドローカルストレージ(TLS)に保存・復元する役割を担っています。
変更前は、これらのルーチンはMRC
命令でTLSベースポインタを取得した後、liblink
リンカがその後の命令を「魔法のように」調整して、g
とm
のTLSコピーが格納されているアドレスを指すようにしていました。この「魔法」が、リンカモードによる挙動の不一致やWORD
命令の誤った書き換えといった問題の原因でした。
変更後のコードは、この「魔法」を排除し、より明示的で堅牢な方法でTLSにアクセスします。
runtime·save_gm
(変更後)
TEXT runtime·save_gm(SB),NOSPLIT,$0
MRC 15, 0, R0, C13, C0, 3 // fetch TLS base pointer
// $runtime.tlsgm(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
ADD R11, R0
MOVW g, 0(R0)
MOVW m, 4(R0)
RET
MRC 15, 0, R0, C13, C0, 3
:- この命令は、ARMのCP15コプロセッサ(システム制御コプロセッサ)のC13レジスタ(Thread ID Register)から値をR0レジスタに読み込みます。
- C13レジスタは、通常、現在のスレッドのTLSベースポインタを保持しています。したがって、この命令の実行後、R0にはTLS領域の基底アドレスが格納されます。
MOVW $runtime·tlsgm(SB), R11
:$runtime·tlsgm(SB)
は、Goのリンカが解決する特別なシンボルです。- このシンボルは、TLSベースポインタからGoランタイムの
g
とm
のポインタが格納されているスレッドローカルストレージへのオフセット値(アドレス)を表します。 MOVW
命令は、このオフセット値をR11レジスタにロードします。- この命令は、リンカによって
R_ARM_TLS_LE32
リロケーションとして処理され、実行時に正しいオフセットがR11にロードされるようにします。
ADD R11, R0
:- R0(TLSベースポインタ)にR11(
g
とm
ストレージへのオフセット)を加算します。 - この結果、R0は、現在のスレッドのTLS領域内で
g
とm
のポインタが実際に格納されているメモリ位置を正確に指すようになります。
- R0(TLSベースポインタ)にR11(
MOVW g, 0(R0)
:- Goランタイムの
g
レジスタ(現在のgoroutineポインタ)の値を、R0が指すアドレス(TLS内のg
ストレージ)に保存します。
- Goランタイムの
MOVW m, 4(R0)
:- Goランタイムの
m
レジスタ(現在のmachineポインタ)の値を、R0が指すアドレスから4バイトオフセットした位置(TLS内のm
ストレージ)に保存します。ARMではポインタは4バイトなので、g
の直後にm
が格納されます。
- Goランタイムの
RET
:- 関数から戻ります。
runtime·load_gm
(変更後)
runtime·load_gm
はsave_gm
の逆の操作を行います。
TEXT runtime·load_gm(SB),NOSPLIT,$0
MRC 15, 0, R0, C13, C0, 3 // fetch TLS base pointer
// $runtime.tlsgm(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
ADD R11, R0
MOVW 0(R0), g
MOVW 4(R0), m
RET
- 最初の3行は
save_gm
と同じで、R0をg
とm
のTLSストレージを指すように設定します。 MOVW 0(R0), g
:- R0が指すアドレス(TLS内の
g
ストレージ)からg
レジスタに値をロードします。
- R0が指すアドレス(TLS内の
MOVW 4(R0), m
:- R0が指すアドレスから4バイトオフセットした位置(TLS内の
m
ストレージ)からm
レジスタに値をロードします。
- R0が指すアドレスから4バイトオフセットした位置(TLS内の
RET
:- 関数から戻ります。
この新しいアプローチにより、TLSへのアクセスがリンカの「魔法」に依存せず、runtime.tlsgm
という明確なシンボルと標準的なリロケーションメカニズムを通じて行われるため、コードの堅牢性、可読性、およびメンテナンス性が大幅に向上しました。
関連リンク
- ARMアーキテクチャリファレンスマニュアル:
MRC
命令やコプロセッサレジスタに関する詳細な情報。- ARM Architecture Reference Manuals (具体的なバージョンはARMv7-A/R Architecture Reference Manualなど)
- ELF for ARM Architecture:
R_ARM_TLS_LE32
やR_ARM_TLS_IE32
などのリロケーションタイプに関する詳細。 - Go言語のCgoドキュメント:
- Goランタイムの
g
とm
に関する情報:- Go's work-stealing scheduler (スケジューラと
g
/m
の関連性) - Go scheduler: M, P, G (より詳細な解説)
- Go's work-stealing scheduler (スケジューラと
参考にした情報源リンク
- コミットメッセージ自体 (
./commit_data/18346.txt
) - Go言語のソースコード (
src/cmd/ld/data.c
,src/cmd/ld/lib.c
,src/liblink/asm5.c
,src/liblink/obj5.c
,src/pkg/runtime/asm_arm.s
) - ARMアーキテクチャに関する一般的な知識
- ELFファイルフォーマットとリロケーションに関する一般的な知識
- Goランタイムの内部構造に関する一般的な知識
- ELF for the ARM Architecture (R_ARM_TLS_LE32, R_ARM_TLS_IE32の確認)
- ARM Architecture Reference Manuals (MRC命令の確認)
- Go and Cgo
- Go scheduler: M, P, G