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

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

このコミットは、Go言語のARMアーキテクチャにおけるCgo(C言語との相互運用機能)のTLS(Thread Local Storage)実装に関するバグ修正です。特に、liblinkリンカとGoランタイムがTLSベースポインタとg(goroutine)およびm(machine)構造体へのオフセットを扱う方法に存在した、複雑で誤解を招くような挙動を根本的に改善することを目的としています。以前の実装では、内部リンカと外部リンカでMRC命令の解釈が異なったり、liblinkWORD命令を誤って書き換えたりする問題がありました。このコミットでは、リンカが提供する特別なシンボル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命令を導入するのではなく、アセンブラでマクロ展開される形であったため、liblinkWORD命令を書き換える際に、本来その定数を期待しているコードに対して予期せぬ結果をもたらす可能性がありました。さらに、特定のオフセット値が偶然にも8であったために正しく動作していたものの、汎用的な解決策ではなく、非常に混乱を招くものでした。

このコミットでは、これらの問題を根本的に解決するために、既存の複雑な実装をすべて破棄し、新しいアプローチを導入しています。新しいアプローチでは、リンカが提供するruntime.tlsgmというシンボルを使用します。このシンボルは、ハードウェアが提供するTLSベースレジスタからg(goroutine構造体)とm(machine構造体)のストレージへのオフセット値(アドレス)を持ちます。このシンボルへの参照は、リンカモード(内部または外部)に応じて適切なTLSリロケーション(R_ARM_TLS_LE32リロケーションと同じセマンティクスを持つ)を生成します。

runtime.tlsgmシンボルは、runtime.load_gmruntime.save_gmの2つのルーチンでのみ使用されます。これらのルーチンでは、まずMRC命令でTLSベースポインタを取得し、次にruntime.tlsgmのオフセットをMOVWでレジスタにロードし、最後にADD命令でTLSベースポインタにオフセットを加算することで、スレッドローカルなgmのストレージを指すようにします。

この変更は、ARM上の共有ライブラリの生成を一時的に壊す可能性があると述べられています。これは、MOVW命令がグローバルオフセットテーブル(GOT)と異なるリロケーションタイプを使用するように書き換えられる必要があるためです。しかし、このコミットの目的は、まずサポートされている機能が再び動作するようにすることであり、サポートされていない機能(共有ライブラリ)については後で対処する方針が示されています。

変更の背景

このコミットの背景には、Go言語のARMアーキテクチャにおけるCgoとTLSの複雑な相互作用に起因する複数の問題がありました。

  1. リンカモードによる挙動の不一致: Goのツールチェインは、内部リンカと外部リンカ(通常はGCCなど)の両方を使用できます。TLSをARMに追加した際、MRC命令(ARMのコプロセッサレジスタ操作命令で、TLSベースポインタの取得によく使われる)の解釈やコード生成が、どちらのリンカを使用するかによって異なっていました。これは、コンパイル時には最終的にどちらのリンカが使用されるか分からないため、非常に不安定な挙動を引き起こしていました。
  2. liblinkによるWORD命令の誤った書き換え: 以前の実装では、MRC命令がアセンブラでマクロ展開される形で導入されていました。これにより、liblink(Goの内部リンカ)が、本来特定の定数を期待している可能性のあるWORD命令を、TLS関連のコードとして誤って書き換えてしまう問題が発生していました。これは、予期せぬバグやクラッシュにつながる可能性がありました。
  3. マジックナンバーと混乱: コード内には、TLSオフセットに関連する値として8という定数が使われていましたが、これは偶然にも正しい値であっただけで、その意味が不明瞭であり、汎用的な解決策ではありませんでした。このようなマジックナンバーは、コードの可読性を低下させ、将来のメンテナンスを困難にします。
  4. 共有ライブラリ生成への影響: TLSの扱いは、特に共有ライブラリ(ダイナミックリンクライブラリ)の生成において複雑になります。以前の実装は、共有ライブラリの生成を考慮していなかったか、不適切に扱っていた可能性があります。このコミットの変更も、一時的に共有ライブラリの生成に影響を与える可能性が指摘されていますが、まずは基本的なCgoとTLSの機能が正しく動作することを優先しています。

これらの問題は、GoプログラムがCgoを介してCライブラリと連携する際に、スレッドローカルなデータ(特にGoランタイムのgmポインタ)のアクセスに不整合やエラーを引き起こす可能性がありました。このコミットは、これらの根本的な問題を解決し、ARM上でのCgoの安定性と信頼性を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下の技術的な概念を把握しておく必要があります。

  1. Cgo: Go言語とC言語のコードを相互に呼び出すためのGoの機能です。Cgoを使用すると、Goプログラムから既存のCライブラリを利用したり、CコードからGo関数を呼び出したりできます。Cgoは、GoランタイムとCランタイム(通常はglibcなどの標準Cライブラリ)の間で、スレッド、メモリ、レジスタなどのコンテキスト切り替えを伴うため、特に低レベルのアーキテクチャ固有の考慮事項が必要になります。
  2. 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のような形式で行われます。
  3. TLS (Thread Local Storage): スレッドローカルストレージは、各スレッドが独自のデータコピーを持つことを可能にするメモリ領域です。グローバル変数とは異なり、TLSに格納されたデータは、同じプロセス内の他のスレッドからは直接アクセスできません。Goランタイムでは、現在のgoroutine(g)と現在のOSスレッド(m)のポインタをTLSに格納することが一般的です。これにより、Cgo呼び出しなどでOSスレッドが切り替わった際に、Goランタイムが正しいgmのコンテキストを迅速に取得できます。
  4. Goのgm:
    • g (goroutine): Go言語の軽量スレッドです。Goランタイムによってスケジューリングされ、OSスレッド上で実行されます。各gは独自のスタックを持ちます。
    • m (machine): OSスレッドを表します。Goランタイムは、複数のgを少数のm上で多重化して実行します。Cgo呼び出しを行う際など、Goランタイムは現在のgmのポインタをTLSに保存し、Cコードの実行後にそれらを復元する必要があります。
  5. リンカ: コンパイラによって生成されたオブジェクトファイル(機械語コードとデータ)を結合し、実行可能ファイルやライブラリを生成するプログラムです。
    • 内部リンカ (liblink): Go言語のツールチェインに組み込まれている独自のリンカです。Goの特殊なランタイム要件やクロスコンパイルの容易さを実現するために使用されます。
    • 外部リンカ (e.g., GCC): システムにインストールされている標準的なリンカです。Cgoを使用する場合など、GoのコードがCライブラリとリンクされる際に使用されることがあります。
  6. リロケーション (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)を介してアクセスされる場合に用いられます。
  7. GOT (Global Offset Table): 共有ライブラリにおいて、グローバル変数や関数のアドレスを間接的に参照するために使用されるテーブルです。PICでは、コードがメモリ上のどこにロードされても正しく動作するように、絶対アドレスではなく相対アドレスを使用します。GOTは、実行時に実際の絶対アドレスが解決され、そこに格納されます。

技術的詳細

このコミットの技術的詳細を理解するには、以前のTLS実装の問題点と、新しいruntime.tlsgmシンボルに基づくアプローチの仕組みを深く掘り下げる必要があります。

以前のTLS実装の問題点

コミットメッセージが指摘するように、以前のARM TLS実装はいくつかの深刻な問題を抱えていました。

  1. MRC命令のリンカ依存の挙動:
    • MRC 15, 0, R0, C13, C0, 3という命令は、ARMのCP15コプロセッサ(システム制御コプロセッサ)のC13レジスタ(Thread ID Register)から値をR0レジスタに読み込むものです。このレジスタは通常、TLSベースポインタを保持しています。
    • 問題は、Goの内部リンカ(liblink)と外部リンカ(例えばGCCのリンカ)が、この命令の後のコード生成やリロケーションの処理を異なる方法で行っていたことです。コンパイル時にはどちらのリンカが使われるか不明なため、生成されるコードがリンカによって変わるという不安定な状態でした。
  2. liblinkによるWORD命令の誤った書き換え:
    • 以前の実装では、TLS関連のコードがアセンブラでマクロ展開され、MRC命令の後に特定のWORD命令(データとして埋め込まれた32ビット値)が続くことがありました。
    • liblinkは、このWORD命令をTLSオフセットの調整のために書き換えるロジックを持っていました。しかし、このWORD命令がTLSオフセットとは無関係な、本来の定数として使用されるべき場所であった場合、liblinkの書き換えによってその定数が破壊され、予期せぬバグやクラッシュを引き起こす可能性がありました。
  3. マジックナンバーと混乱:
    • 8というオフセット値がコード内で使われていましたが、これはELF ARMにおけるスレッドポインタがスレッドローカルデータブロックの開始から8バイト手前にあるという「基本的な定数」と、Go自身のTLSストレージがたまたま8バイトを占めるという二つの異なる意味を持っていました。この偶然の一致が、コードを混乱させ、汎用性を欠くものにしていました。

新しいruntime.tlsgmアプローチ

これらの問題を解決するため、コミットは「すべてを捨て去る」という大胆なアプローチを取り、runtime.tlsgmという新しいリンカ提供シンボルを導入しました。

  1. runtime.tlsgmシンボルの導入:
    • runtime.tlsgmは、Goランタイムのgmのストレージが、ハードウェアが提供するTLSベースレジスタからどれだけオフセットしているかを示す値(アドレス)を持つ特別なシンボルです。
    • このシンボルは、src/cmd/ld/data.cdodata関数内でSTLSBSSタイプとして定義され、そのvalue0に設定されます。これは、TLSベースポインタからの相対オフセットであることを示唆しています。
    • src/cmd/ld/lib.cでは、gmsymruntime.tlsgmに対応)のreachableフラグが常に1に設定され、リンカがこのシンボルを常に考慮するようにします。
  2. R_ARM_TLS_LE32リロケーションの利用:
    • runtime.tlsgmへの参照は、src/liblink/asm5.casmout関数内で、D_TLSタイプのリロケーションとして処理されます。
    • このリロケーションは、外部リンカが提供するR_ARM_TLS_LE32リロケーションと全く同じセマンティクスを持つように設計されています。R_ARM_TLS_LE32は、TLS変数のオフセットをTLSベースポインタからの相対アドレスとして解決するための標準的なARMリロケーションタイプです。
    • これにより、内部リンカと外部リンカの両方で、runtime.tlsgmへの参照が適切に解決され、リンカモードに依存しない一貫した挙動が保証されます。
    • src/cmd/ld/data.crelocsym関数では、ELF ARMの場合、D_TLSリロケーションに対して、TLSベースポインタからのオフセットに8バイトを加算するロジックが追加されています。これは、ELF ARMのTLSポインタがデータブロックの開始から8バイト手前にあるという仕様に対応するためです。
  3. runtime.load_gmruntime.save_gmの変更:
    • src/pkg/runtime/asm_arm.s内のruntime·save_gmruntime·load_gmルーチンが、新しいアプローチを使用するように変更されました。
    • これらのルーチンは、まずMRC命令でTLSベースポインタをR0レジスタに取得します。
    • 次に、MOVW $runtime·tlsgm(SB), R11命令でruntime.tlsgmシンボルの値(TLSベースポインタからのオフセット)をR11レジスタにロードします。
    • 最後に、ADD R11, R0命令で、R0(TLSベースポインタ)にR11(オフセット)を加算し、R0がgmのストレージを直接指すようにします。
    • これにより、以前のliblinkによる「魔法のような」オフセット調整が不要になり、コードがより明示的で理解しやすくなりました。
  4. 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アクセスを実現し、将来的な拡張やデバッグが容易になりました。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  1. src/pkg/runtime/asm_arm.s:

    • runtime·save_gmruntime·load_gmという2つのアセンブリルーチンが大幅に変更されています。
    • 以前はliblinkによる「魔法のような」オフセット調整に依存していましたが、新しいコードではruntime·tlsgmシンボルを明示的に使用してgmの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
    
  2. src/liblink/obj5.c:

    • このファイルからは、以前のTLSレジスタフェッチ(MRC命令の書き換え)と、それに続くMOVWADDSUB命令による複雑な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;
     	}
    
  3. src/cmd/ld/data.c:

    • relocsym関数内のD_TLSリロケーションの処理に、ELF ARMの場合のオフセット調整(o = 8 + r->sym->value;)が追加されています。
    • dodata関数内で、STLSBSSシンボル(特にruntime.tlsgm)のvalue0に設定されるロジックが追加されています。
  4. src/liblink/asm5.c:

    • asmout関数内で、runtime.tlsgmシンボルに対するD_TLSリロケーションの生成ロジックが追加されています。これにより、runtime.tlsgmへの参照が適切にTLSリロケーションとして扱われるようになります。
  5. src/cmd/ld/lib.c:

    • loadlib関数内で、gmsymruntime.tlsgmに対応)のreachableフラグが常に1に設定されるようになりました。

これらの変更は、GoのARM TLS実装の根本的な再設計を反映しており、特にアセンブリコードとリンカの挙動に大きな影響を与えています。

コアとなるコードの解説

このコミットの核となる変更は、src/pkg/runtime/asm_arm.s内のruntime·save_gmruntime·load_gmアセンブリルーチンにあります。これらのルーチンは、Cgo呼び出しの前後でGoランタイムのg(goroutine)とm(machine)ポインタをスレッドローカルストレージ(TLS)に保存・復元する役割を担っています。

変更前は、これらのルーチンはMRC命令でTLSベースポインタを取得した後、liblinkリンカがその後の命令を「魔法のように」調整して、gmの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
  1. MRC 15, 0, R0, C13, C0, 3:
    • この命令は、ARMのCP15コプロセッサ(システム制御コプロセッサ)のC13レジスタ(Thread ID Register)から値をR0レジスタに読み込みます。
    • C13レジスタは、通常、現在のスレッドのTLSベースポインタを保持しています。したがって、この命令の実行後、R0にはTLS領域の基底アドレスが格納されます。
  2. MOVW $runtime·tlsgm(SB), R11:
    • $runtime·tlsgm(SB)は、Goのリンカが解決する特別なシンボルです。
    • このシンボルは、TLSベースポインタからGoランタイムのgmのポインタが格納されているスレッドローカルストレージへのオフセット値(アドレス)を表します。
    • MOVW命令は、このオフセット値をR11レジスタにロードします。
    • この命令は、リンカによってR_ARM_TLS_LE32リロケーションとして処理され、実行時に正しいオフセットがR11にロードされるようにします。
  3. ADD R11, R0:
    • R0(TLSベースポインタ)にR11(gmストレージへのオフセット)を加算します。
    • この結果、R0は、現在のスレッドのTLS領域内でgmのポインタが実際に格納されているメモリ位置を正確に指すようになります。
  4. MOVW g, 0(R0):
    • Goランタイムのgレジスタ(現在のgoroutineポインタ)の値を、R0が指すアドレス(TLS内のgストレージ)に保存します。
  5. MOVW m, 4(R0):
    • Goランタイムのmレジスタ(現在のmachineポインタ)の値を、R0が指すアドレスから4バイトオフセットした位置(TLS内のmストレージ)に保存します。ARMではポインタは4バイトなので、gの直後にmが格納されます。
  6. RET:
    • 関数から戻ります。

runtime·load_gm (変更後)

runtime·load_gmsave_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
  1. 最初の3行はsave_gmと同じで、R0をgmのTLSストレージを指すように設定します。
  2. MOVW 0(R0), g:
    • R0が指すアドレス(TLS内のgストレージ)からgレジスタに値をロードします。
  3. MOVW 4(R0), m:
    • R0が指すアドレスから4バイトオフセットした位置(TLS内のmストレージ)からmレジスタに値をロードします。
  4. RET:
    • 関数から戻ります。

この新しいアプローチにより、TLSへのアクセスがリンカの「魔法」に依存せず、runtime.tlsgmという明確なシンボルと標準的なリロケーションメカニズムを通じて行われるため、コードの堅牢性、可読性、およびメンテナンス性が大幅に向上しました。

関連リンク

参考にした情報源リンク

  • コミットメッセージ自体 (./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