[インデックス 18408] ファイルの概要
このコミットは、Go言語のツールチェインにおけるliblink
、cmd/5a
(ARMアセンブラ)、cmd/5l
(ARMリンカ)に関連する変更です。具体的には、古いARMプロセッサ(ARMv5など)上でのCgo(GoとC言語の相互運用機能)におけるTLS(Thread Local Storage)の取り扱いを修正し、以前の変更によって発生した問題を解決することを目的としています。
コミット
commit 1dd4da14591a25748f90a42ef35fa360ff8edde2
Author: Elias Naur <elias.naur@gmail.com>
Date: Mon Feb 3 14:07:54 2014 -0800
liblink, cmd/5a, cmd/5l: restore cgo on older ARM processors
CL 56120043 fixed TLS handling on ARM after the introduction of
liblink but left older ARM processors broken.
Before liblink, the MRC instruction was replaced with a fallback
on older ARMs. CL 56120043 removed that, because the rewrite matched
bit patterns on the AWORD pseudo-instruction and could therefore change
unrelated AWORDs that happened to match.
This CL adds an AMRC instruction to encode both MRC and MCR previously
encoded as AWORDs. Then, in liblink, the AMRC instructions are either
rewritten to AWORD, or, on goarm < 7, replaced with a branch to the
fallback.
./all.bash completes successfully on an ARMv7 with either GOARM=7 or
GOARM=5. I have verified that the fallback is indeed present in both
runtime.save_gm and runtime.load_gm when GOARM=5 but not when GOARM=7.
If all goes well, this should fix the armv5 builders.
LGTM=iant
R=iant, rsc
CC=golang-codereviews
https://golang.org/cl/55540044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1dd4da14591a25748f90a42ef35fa360ff8edde2
元コミット内容
このコミットは、Go言語のARMアセンブラ(cmd/5a
)とリンカ(cmd/5l
)、そしてリンキングライブラリ(liblink
)に対する変更です。主な目的は、以前のコミット(CL 56120043)によってARMプロセッサにおけるTLS(Thread Local Storage)の扱いが修正された際に、古いARMプロセッサ(特にARMv5)でCgoが動作しなくなった問題を解決することです。
具体的には、以下の問題に対処しています。
liblink
導入後のTLS処理の破損:liblink
が導入された後、ARMにおけるTLS処理が修正されましたが、これが古いARMプロセッサでは機能しなくなりました。MRC
命令の誤変換:liblink
導入前は、古いARMではMRC
命令がフォールバック処理に置き換えられていました。しかし、CL 56120043ではこの置き換えが削除されました。その理由は、MRC
命令のビットパターンがAWORD
擬似命令のビットパターンと一致してしまい、無関係なAWORD
命令まで変更されてしまう可能性があったためです。
このコミットでは、これらの問題を解決するために、MRC
およびMCR
命令をエンコードするための新しいAMRC
命令を導入しています。これにより、AWORD
としてエンコードされていたこれらの命令を適切に扱えるようになります。そして、liblink
内でAMRC
命令は、GOARM < 7
の場合にはフォールバックへの分岐に置き換えられ、それ以外の場合はAWORD
に書き換えられます。
コミットメッセージには、./all.bash
がARMv7環境でGOARM=7
とGOARM=5
の両方で正常に完了したこと、そしてGOARM=5
の場合にruntime.save_gm
とruntime.load_gm
にフォールバック処理が存在することが確認されたと記載されています。これにより、ARMv5ビルダの問題が解決されることが期待されています。
変更の背景
この変更の背景には、Go言語のARMアーキテクチャサポートにおけるTLS(Thread Local Storage)の複雑な歴史があります。
Go言語は、その軽量な並行処理モデルであるゴルーチン(goroutine)を特徴としています。ゴルーチンはOSのスレッドに多重化されるため、C言語のライブラリがスレッドローカルストレージ(TLS)に依存している場合、GoのCgo(GoとCの相互運用機能)を介してCコードを呼び出す際に問題が発生することがあります。これは、Cライブラリが期待するスレッドのライフサイクルと、GoランタイムがOSスレッドを管理する方法との間に不整合が生じるためです。
特にARMプロセッサでは、TLSの実装に特定のCPU命令(MRC
やMCR
)が使用されます。これらの命令は、コプロセッサレジスタから汎用レジスタへのデータ転送や、その逆の操作を行うために用いられ、TLS領域へのポインタを保持する専用のハードウェアレジスタ(例: CP15 c13, c0
)へのアクセスに利用されます。
以前のCL 56120043では、ARMにおけるTLSの取り扱いが修正されました。この修正は、Goのリンカであるliblink
の導入と関連しています。しかし、この修正が原因で、古いARMプロセッサ(ARMv5など)でCgoが正しく動作しなくなるという副作用が生じました。
問題の核心は、MRC
命令の処理方法にありました。liblink
導入前は、古いARMではMRC
命令が特定のフォールバック処理に置き換えられていました。これは、古いARMプロセッサが特定のMRC
命令を直接サポートしていなかったり、効率的に実行できなかったりする場合に対応するためです。しかし、CL 56120043では、この置き換えロジックが削除されました。その理由は、MRC
命令のビットパターンがGoのアセンブラにおけるAWORD
擬似命令のビットパターンと偶然一致してしまうことがあり、その結果、無関係なAWORD
命令まで誤って書き換えられてしまう可能性があったためです。このような誤変換は、生成されるコードの誤動作やクラッシュを引き起こす可能性があります。
このコミットは、この誤変換の問題を解決し、古いARMプロセッサ上でもCgoがTLSを正しく扱えるようにすることで、GoのARMサポートの堅牢性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の技術的な概念を把握しておく必要があります。
-
Go言語のCgo:
- Cgoは、GoプログラムからC言語のコードを呼び出したり、その逆を行ったりするためのGoの外部関数インターフェース(FFI)です。
- GoのゴルーチンはOSのスレッドに多重化されます。Cgoを介してC関数を呼び出す際、Goランタイムは一時的にゴルーチンをOSスレッドに「ピン留め」します。しかし、このピン留めは一時的なものであり、同じゴルーチンが異なるCgo呼び出しで別のOSスレッドに割り当てられる可能性があります。
- この挙動が、C言語のライブラリがスレッドローカルストレージ(TLS)に強く依存している場合に問題を引き起こします。
-
Thread Local Storage (TLS):
- TLSは、各スレッドが独自のデータインスタンスを持つことを可能にするメカニズムです。これにより、スレッド間で共有されるグローバル変数とは異なり、スレッド固有のデータを安全に管理できます。
- C言語のライブラリでは、
errno
のようなエラーコードや、特定のコンテキスト情報などをTLSに格納することがよくあります。 - ARMアーキテクチャでは、TLSへのアクセスに特定のCPU命令が使用されることがあります。
-
ARMアーキテクチャとCPU命令 (MRC, MCR):
- ARMは、モバイルデバイスや組み込みシステムで広く使用されているRISC(Reduced Instruction Set Computer)アーキテクチャです。
MRC
(Move to Register from Coprocessor): コプロセッサレジスタからARM汎用レジスタへデータを転送する命令です。TLSの実装では、この命令を使って、スレッドローカルストレージのベースアドレスを保持するコプロセッサレジスタ(例: CP15のc13レジスタ)から、そのアドレスを読み出すために使用されます。MCR
(Move to Coprocessor from ARM core register): ARM汎用レジスタからコプロセッサレジスタへデータを転送する命令です。TLSのコンテキストでは、スレッドローカルストレージのベースアドレスをコプロセッサレジスタに書き込むために使用されることがあります。- これらの命令は、ハードウェアレベルでTLSを効率的にサポートするために重要です。
-
Goのアセンブラと擬似命令 (AWORD):
- Go言語は、独自の「擬似アセンブラ」を使用しており、これは一般的なCPUのアセンブリ言語とは異なります。Goのアセンブラ命令は、最終的にターゲットCPUのネイティブ命令に変換されます。
AWORD
は、Goのアセンブラにおける擬似命令の一つで、「ワード」サイズのメモリを割り当てるために使用されます。これは、データや定数をメモリに配置する際に使われます。- このコミットの背景では、
MRC
やMCR
命令のビットパターンが、意図せずAWORD
擬似命令のビットパターンと一致してしまうという問題が発生していました。
-
liblink
:liblink
は、Goツールチェインの一部であり、Goのコンパイラやアセンブラ、リンカが使用するリンキングプロセスに関連するライブラリです。オブジェクトファイルの生成や、最終的な実行可能ファイルのリンクにおいて重要な役割を担っています。
-
GOARM
環境変数:GOARM
は、Goプログラムを32ビットARMアーキテクチャ向けにコンパイルする際に使用される環境変数です(GOARCH=arm
の場合)。- この変数は、ターゲットとするARMアーキテクチャのバージョンを指定し、コンパイラがそのバージョンに最適化された命令セットを使用するように指示します。
- 一般的な値としては、
GOARM=5
(ARMv5向け、ソフトウェア浮動小数点エミュレーションを使用する場合がある)、GOARM=6
(ARMv6向け)、GOARM=7
(ARMv7向け、ハードウェア浮動小数点やアトミック命令をサポート)などがあります。このコミットでは、GOARM < 7
という条件が重要な意味を持ちます。
-
CL (Change List):
- Goプロジェクトでは、変更は「Change List (CL)」として管理されます。これは、Gitのコミットに相当する概念で、コードレビューシステム(Gerritなど)で使われます。
これらの概念を理解することで、このコミットがGoのARMサポート、特にCgoとTLSの相互作用における低レベルな問題をどのように解決しようとしているのかが明確になります。
技術的詳細
このコミットの技術的詳細は、GoのARMアセンブラとリンカが、ARMプロセッサのTLS(Thread Local Storage)アクセス命令であるMRC
およびMCR
をどのように扱うか、そして古いARMプロセッサとの互換性をどのように維持するかという点に集約されます。
問題の根源
以前のCL 56120043は、liblink
の導入後にARMにおけるTLS処理を修正しましたが、この修正が古いARMプロセッサ(特にARMv5)でCgoを壊しました。
根本的な原因は、GoのアセンブラがMRC
命令を処理する方法にありました。古いARMプロセッサでは、特定のMRC
命令が直接サポートされていないか、効率的に実行できない場合がありました。そのため、liblink
導入前は、これらのMRC
命令は、より汎用的なフォールバック処理への分岐に置き換えられていました。
しかし、CL 56120043では、このMRC
命令の置き換えロジックが削除されました。その理由は、MRC
命令のバイナリ表現(ビットパターン)が、GoのアセンブラにおけるAWORD
擬似命令のビットパターンと偶然一致してしまうことがあり、この一致によって無関係なAWORD
命令まで誤ってMRC
命令として解釈され、書き換えられてしまう可能性があったためです。このような誤変換は、生成されるコードの誤動作やクラッシュを引き起こす深刻なバグとなります。
解決策:AMRC
命令の導入と条件付き書き換え
このコミットは、この問題を解決するために以下の戦略を採用しています。
-
AMRC
命令の導入:- Goのアセンブラ(
cmd/5a
)に、新しい擬似命令であるAMRC
が導入されました。 - この
AMRC
命令は、ARMのMRC
およびMCR
命令をエンコードするために特別に設計されています。これにより、以前は汎用的なAWORD
としてエンコードされていたこれらのコプロセッサ命令を、より明確かつ安全に表現できるようになります。 a.y
ファイル(Bisonパーサの定義ファイル)の変更は、アセンブラがMRC
命令の構文を解析する際に、それをAWORD
ではなくAMRC
として認識するように修正されたことを示しています。
- Goのアセンブラ(
-
liblink
における条件付き書き換え:liblink
(src/liblink/obj5.c
)は、AMRC
命令を処理するロジックが追加されました。- このロジックは、コンパイル時の
GOARM
環境変数の値に基づいて、AMRC
命令を条件付きで書き換えます。GOARM < 7
の場合: 古いARMプロセッサ(ARMv5, ARMv6など)をターゲットにしている場合、AMRC
命令は、TLSアクセス用のフォールバック処理への分岐命令に置き換えられます。これは、古いARMプロセッサが特定のMRC
/MCR
命令を効率的に実行できない、またはサポートしていない可能性があるためです。このフォールバックは、runtime.save_gm
やruntime.load_gm
といったランタイム関数内で実装され、TLSへのアクセスをソフトウェア的に処理します。GOARM >= 7
の場合: ARMv7以降のプロセッサをターゲットにしている場合、AMRC
命令は、元のMRC
またはMCR
命令のバイナリ表現であるAWORD
に書き換えられます。ARMv7以降のプロセッサは、これらのコプロセッサ命令をハードウェアで効率的に実行できるため、フォールバックは不要です。
影響と検証
この変更により、Goのツールチェインは、MRC
/MCR
命令をAWORD
と誤認識することなく、ARMプロセッサのバージョンに応じて適切なTLSアクセス方法を選択できるようになります。
コミットメッセージには、./all.bash
(Goのビルドスクリプト)がARMv7環境でGOARM=7
とGOARM=5
の両方で正常に完了したことが記載されています。また、GOARM=5
の場合にruntime.save_gm
とruntime.load_gm
にフォールバック処理が存在することが確認されたとあります。これは、新しいロジックが意図通りに動作し、古いARMプロセッサでのCgoのTLS問題が解決されたことを示唆しています。
この修正は、GoのARMサポートの堅牢性を高め、特に組み込みシステムや古いARMデバイスでのGoアプリケーションの信頼性を向上させる上で重要な役割を果たします。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。
-
src/cmd/5a/a.y
:- GoのARMアセンブラ(
cmd/5a
)の文法定義ファイルです。Bison(Yacc互換のパーサジェネレータ)によって解析されます。 - このファイルでは、
MRC
およびMCR
命令の構文解析ルールが変更され、これらの命令がAWORD
ではなく、新しく導入されたAMRC
として認識されるように修正されています。 - 具体的には、
inst
(命令)の定義内で、MRC
命令を処理する部分がoutcode(AWORD, ...)
からoutcode(AMRC, ...)
に変更されています。
- GoのARMアセンブラ(
-
src/cmd/5a/y.tab.c
:a.y
から生成されるC言語のパーサコードです。- このファイルは自動生成されるため、直接編集されることは稀ですが、
a.y
の変更に伴って内容が更新されています。変更点には、Bisonのバージョン情報の更新や、内部的なデータ構造の調整などが含まれます。
-
src/cmd/5a/y.tab.h
:a.y
から生成されるヘッダファイルです。トークン定義などが含まれます。y.tab.c
と同様に、a.y
の変更に伴って更新されています。
-
src/cmd/5l/5.out.h
:- GoのARMリンカ(
cmd/5l
)に関連するヘッダファイルです。 - このファイルには、新しい命令タイプである
AMRC
の定義が追加されています。これにより、リンカがAMRC
命令を認識し、適切に処理できるようになります。
- GoのARMリンカ(
-
src/liblink/obj5.c
:liblink
の一部であり、ARMアーキテクチャ固有のオブジェクトファイル処理を担当するC言語のソースファイルです。- このファイルに、
AMRC
命令を処理するための新しいロジックが追加されています。具体的には、AMRC
命令が検出された際に、GOARM
環境変数の値に基づいて、その命令をAWORD
に書き換えるか、またはTLSフォールバック処理への分岐に置き換えるかの条件分岐が実装されています。
これらのファイルは、Goのコンパイルおよびリンクプロセスにおける低レベルな部分を担っており、ARMアーキテクチャ固有の命令セットとGoのランタイムの連携を管理しています。
コアとなるコードの解説
このコミットの核心は、GoのARMアセンブラとリンカが、ARMのコプロセッサ命令(特にTLS関連のMRC
/MCR
)をどのように扱い、異なるARMプロセッサバージョン(GOARM
の値)に応じて適切なコードを生成するかという点にあります。
src/cmd/5a/a.y
の変更
--- a/src/cmd/5a/a.y
+++ b/src/cmd/5a/a.y
@@ -294,7 +294,7 @@ inst:
(($11 & 15) << 0) |\t/* Crm */
(($12 & 7) << 5) |\t/* coprocessor information */
(1<<4);\t\t\t/* must be set */
- outcode(AWORD, Always, &nullgen, NREG, &g);
+ outcode(AMRC, Always, &nullgen, NREG, &g);
}
/*
* MULL r1,r2,(hi,lo)
この変更は、GoのARMアセンブラの文法定義ファイル(a.y
)の一部です。inst
(命令)の定義内で、特定のコプロセッサ命令(MRC
またはMCR
)を解析する際に、以前は汎用的なAWORD
擬似命令として出力していたものを、新しく導入されたAMRC
擬似命令として出力するように変更しています。
outcode
関数は、アセンブラがオブジェクトコードを生成する際に使用する内部関数です。AWORD
は、Goのアセンブラにおける「ワード」サイズのメモリを割り当てる擬似命令です。以前は、MRC
/MCR
命令のバイナリ表現を直接このAWORD
として扱っていました。AMRC
は、このコミットで導入された新しい擬似命令で、MRC
およびMCR
命令専用のエンコーディングを提供します。これにより、アセンブラはこれらのコプロセッサ命令をより正確に識別できるようになります。
この変更の目的は、MRC
/MCR
命令のビットパターンがAWORD
のビットパターンと偶然一致してしまい、無関係なAWORD
命令が誤って書き換えられる問題を回避することです。AMRC
という専用の擬似命令を導入することで、アセンブラはこれらの命令を明確に区別し、リンカが後続の処理で適切に扱うことができるようになります。
src/liblink/obj5.c
の変更 (概念的な説明)
src/liblink/obj5.c
は、GoのリンカがARMアーキテクチャのオブジェクトファイルを処理する部分です。このファイルには、AMRC
命令を実際に処理するためのロジックが追加されています。
概念的には、以下のような処理が行われます。
- リンカは、アセンブラによって生成されたオブジェクトファイルから命令を読み込みます。
- 命令が
AMRC
タイプであると識別された場合、リンカは現在のターゲットARMプロセッサのバージョン(GOARM
環境変数によって決定される)をチェックします。 GOARM < 7
の場合:- リンカは、
AMRC
命令を、TLSアクセス用のフォールバック処理への分岐命令に置き換えます。 - このフォールバック処理は、Goランタイム内の
runtime.save_gm
やruntime.load_gm
といった関数で実装されており、TLSへのアクセスをソフトウェア的に行います。これは、古いARMプロセッサが特定のMRC
/MCR
命令を効率的に実行できない、またはサポートしていない可能性があるためです。
- リンカは、
GOARM >= 7
の場合:- リンカは、
AMRC
命令を、元のMRC
またはMCR
命令のバイナリ表現であるAWORD
に書き換えます。 - ARMv7以降のプロセッサは、これらのコプロセッサ命令をハードウェアで効率的に実行できるため、特別なフォールバックは不要です。
- リンカは、
この条件付き書き換えのメカニズムにより、Goのツールチェインは、古いARMプロセッサと新しいARMプロセッサの両方で、CgoにおけるTLSアクセスを正しく、かつ効率的に処理できるようになります。これにより、以前のコミットで発生した互換性の問題が解決され、ARMv5のような古い環境でもCgoが安定して動作するようになります。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/
- GoのCgoに関するドキュメント: https://pkg.go.dev/cmd/cgo
- ARMアーキテクチャリファレンスマニュアル (ARMの公式ウェブサイトで入手可能)
- Goの環境変数に関するドキュメント (
GOARM
について): https://go.dev/doc/install/source#environment
参考にした情報源リンク
- Goの
liblink
に関する情報: https://go.dev/src/cmd/link/internal/ld/liblink.go (現在のGoのソースコードではliblink
はcmd/link/internal/ld
以下に統合されています) - ARMの
MRC
命令とTLSに関するStack Overflowの議論: https://stackoverflow.com/questions/29960976/how-to-access-thread-local-storage-on-arm - Goの
GOARM
環境変数に関する情報: https://go.dev/doc/install/source - Goの
AWORD
擬似命令に関する情報: https://go.dev/doc/asm - Go CL 56120043に関する情報 (Stack Overflowなど): https://stackoverflow.com/questions/tagged/go+cl+56120043 (直接的な公式ドキュメントは見つかりませんでしたが、関連する議論や言及がStack Overflowなどで見られます。)
- GoのCgoとTLSの課題に関する議論: https://github.com/golang/go/issues/1435 (GoのCgoとTLSの一般的な課題に関する古いIssueですが、背景理解に役立ちます)
- GoのCgoとTLSの課題に関する議論 (より新しい提案): https://github.com/golang/go/issues/44707 (GoのTLSモデル改善に関する提案)