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

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

このコミットは、Go言語のツールチェインにおけるliblinkcmd/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が動作しなくなった問題を解決することです。

具体的には、以下の問題に対処しています。

  1. liblink導入後のTLS処理の破損: liblinkが導入された後、ARMにおけるTLS処理が修正されましたが、これが古いARMプロセッサでは機能しなくなりました。
  2. MRC命令の誤変換: liblink導入前は、古いARMではMRC命令がフォールバック処理に置き換えられていました。しかし、CL 56120043ではこの置き換えが削除されました。その理由は、MRC命令のビットパターンがAWORD擬似命令のビットパターンと一致してしまい、無関係なAWORD命令まで変更されてしまう可能性があったためです。

このコミットでは、これらの問題を解決するために、MRCおよびMCR命令をエンコードするための新しいAMRC命令を導入しています。これにより、AWORDとしてエンコードされていたこれらの命令を適切に扱えるようになります。そして、liblink内でAMRC命令は、GOARM < 7の場合にはフォールバックへの分岐に置き換えられ、それ以外の場合はAWORDに書き換えられます。

コミットメッセージには、./all.bashがARMv7環境でGOARM=7GOARM=5の両方で正常に完了したこと、そしてGOARM=5の場合にruntime.save_gmruntime.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命令(MRCMCR)が使用されます。これらの命令は、コプロセッサレジスタから汎用レジスタへのデータ転送や、その逆の操作を行うために用いられ、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サポートの堅牢性を向上させることを目的としています。

前提知識の解説

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

  1. Go言語のCgo:

    • Cgoは、GoプログラムからC言語のコードを呼び出したり、その逆を行ったりするためのGoの外部関数インターフェース(FFI)です。
    • GoのゴルーチンはOSのスレッドに多重化されます。Cgoを介してC関数を呼び出す際、Goランタイムは一時的にゴルーチンをOSスレッドに「ピン留め」します。しかし、このピン留めは一時的なものであり、同じゴルーチンが異なるCgo呼び出しで別のOSスレッドに割り当てられる可能性があります。
    • この挙動が、C言語のライブラリがスレッドローカルストレージ(TLS)に強く依存している場合に問題を引き起こします。
  2. Thread Local Storage (TLS):

    • TLSは、各スレッドが独自のデータインスタンスを持つことを可能にするメカニズムです。これにより、スレッド間で共有されるグローバル変数とは異なり、スレッド固有のデータを安全に管理できます。
    • C言語のライブラリでは、errnoのようなエラーコードや、特定のコンテキスト情報などをTLSに格納することがよくあります。
    • ARMアーキテクチャでは、TLSへのアクセスに特定のCPU命令が使用されることがあります。
  3. 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を効率的にサポートするために重要です。
  4. Goのアセンブラと擬似命令 (AWORD):

    • Go言語は、独自の「擬似アセンブラ」を使用しており、これは一般的なCPUのアセンブリ言語とは異なります。Goのアセンブラ命令は、最終的にターゲットCPUのネイティブ命令に変換されます。
    • AWORDは、Goのアセンブラにおける擬似命令の一つで、「ワード」サイズのメモリを割り当てるために使用されます。これは、データや定数をメモリに配置する際に使われます。
    • このコミットの背景では、MRCMCR命令のビットパターンが、意図せずAWORD擬似命令のビットパターンと一致してしまうという問題が発生していました。
  5. liblink:

    • liblinkは、Goツールチェインの一部であり、Goのコンパイラやアセンブラ、リンカが使用するリンキングプロセスに関連するライブラリです。オブジェクトファイルの生成や、最終的な実行可能ファイルのリンクにおいて重要な役割を担っています。
  6. GOARM環境変数:

    • GOARMは、Goプログラムを32ビットARMアーキテクチャ向けにコンパイルする際に使用される環境変数です(GOARCH=armの場合)。
    • この変数は、ターゲットとするARMアーキテクチャのバージョンを指定し、コンパイラがそのバージョンに最適化された命令セットを使用するように指示します。
    • 一般的な値としては、GOARM=5(ARMv5向け、ソフトウェア浮動小数点エミュレーションを使用する場合がある)、GOARM=6(ARMv6向け)、GOARM=7(ARMv7向け、ハードウェア浮動小数点やアトミック命令をサポート)などがあります。このコミットでは、GOARM < 7という条件が重要な意味を持ちます。
  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命令の導入と条件付き書き換え

このコミットは、この問題を解決するために以下の戦略を採用しています。

  1. AMRC命令の導入:

    • Goのアセンブラ(cmd/5a)に、新しい擬似命令であるAMRCが導入されました。
    • このAMRC命令は、ARMのMRCおよびMCR命令をエンコードするために特別に設計されています。これにより、以前は汎用的なAWORDとしてエンコードされていたこれらのコプロセッサ命令を、より明確かつ安全に表現できるようになります。
    • a.yファイル(Bisonパーサの定義ファイル)の変更は、アセンブラがMRC命令の構文を解析する際に、それをAWORDではなくAMRCとして認識するように修正されたことを示しています。
  2. liblinkにおける条件付き書き換え:

    • liblinksrc/liblink/obj5.c)は、AMRC命令を処理するロジックが追加されました。
    • このロジックは、コンパイル時のGOARM環境変数の値に基づいて、AMRC命令を条件付きで書き換えます。
      • GOARM < 7の場合: 古いARMプロセッサ(ARMv5, ARMv6など)をターゲットにしている場合、AMRC命令は、TLSアクセス用のフォールバック処理への分岐命令に置き換えられます。これは、古いARMプロセッサが特定のMRC/MCR命令を効率的に実行できない、またはサポートしていない可能性があるためです。このフォールバックは、runtime.save_gmruntime.load_gmといったランタイム関数内で実装され、TLSへのアクセスをソフトウェア的に処理します。
      • GOARM >= 7の場合: ARMv7以降のプロセッサをターゲットにしている場合、AMRC命令は、元のMRCまたはMCR命令のバイナリ表現であるAWORDに書き換えられます。ARMv7以降のプロセッサは、これらのコプロセッサ命令をハードウェアで効率的に実行できるため、フォールバックは不要です。

影響と検証

この変更により、Goのツールチェインは、MRC/MCR命令をAWORDと誤認識することなく、ARMプロセッサのバージョンに応じて適切なTLSアクセス方法を選択できるようになります。

コミットメッセージには、./all.bash(Goのビルドスクリプト)がARMv7環境でGOARM=7GOARM=5の両方で正常に完了したことが記載されています。また、GOARM=5の場合にruntime.save_gmruntime.load_gmにフォールバック処理が存在することが確認されたとあります。これは、新しいロジックが意図通りに動作し、古いARMプロセッサでのCgoのTLS問題が解決されたことを示唆しています。

この修正は、GoのARMサポートの堅牢性を高め、特に組み込みシステムや古いARMデバイスでのGoアプリケーションの信頼性を向上させる上で重要な役割を果たします。

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

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

  1. src/cmd/5a/a.y:

    • GoのARMアセンブラ(cmd/5a)の文法定義ファイルです。Bison(Yacc互換のパーサジェネレータ)によって解析されます。
    • このファイルでは、MRCおよびMCR命令の構文解析ルールが変更され、これらの命令がAWORDではなく、新しく導入されたAMRCとして認識されるように修正されています。
    • 具体的には、inst(命令)の定義内で、MRC命令を処理する部分がoutcode(AWORD, ...)からoutcode(AMRC, ...)に変更されています。
  2. src/cmd/5a/y.tab.c:

    • a.yから生成されるC言語のパーサコードです。
    • このファイルは自動生成されるため、直接編集されることは稀ですが、a.yの変更に伴って内容が更新されています。変更点には、Bisonのバージョン情報の更新や、内部的なデータ構造の調整などが含まれます。
  3. src/cmd/5a/y.tab.h:

    • a.yから生成されるヘッダファイルです。トークン定義などが含まれます。
    • y.tab.cと同様に、a.yの変更に伴って更新されています。
  4. src/cmd/5l/5.out.h:

    • GoのARMリンカ(cmd/5l)に関連するヘッダファイルです。
    • このファイルには、新しい命令タイプであるAMRCの定義が追加されています。これにより、リンカがAMRC命令を認識し、適切に処理できるようになります。
  5. 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命令を実際に処理するためのロジックが追加されています。

概念的には、以下のような処理が行われます。

  1. リンカは、アセンブラによって生成されたオブジェクトファイルから命令を読み込みます。
  2. 命令がAMRCタイプであると識別された場合、リンカは現在のターゲットARMプロセッサのバージョン(GOARM環境変数によって決定される)をチェックします。
  3. GOARM < 7の場合:
    • リンカは、AMRC命令を、TLSアクセス用のフォールバック処理への分岐命令に置き換えます。
    • このフォールバック処理は、Goランタイム内のruntime.save_gmruntime.load_gmといった関数で実装されており、TLSへのアクセスをソフトウェア的に行います。これは、古いARMプロセッサが特定のMRC/MCR命令を効率的に実行できない、またはサポートしていない可能性があるためです。
  4. GOARM >= 7の場合:
    • リンカは、AMRC命令を、元のMRCまたはMCR命令のバイナリ表現であるAWORDに書き換えます。
    • ARMv7以降のプロセッサは、これらのコプロセッサ命令をハードウェアで効率的に実行できるため、特別なフォールバックは不要です。

この条件付き書き換えのメカニズムにより、Goのツールチェインは、古いARMプロセッサと新しいARMプロセッサの両方で、CgoにおけるTLSアクセスを正しく、かつ効率的に処理できるようになります。これにより、以前のコミットで発生した互換性の問題が解決され、ARMv5のような古い環境でもCgoが安定して動作するようになります。

関連リンク

参考にした情報源リンク