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

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

このコミットは、Goランタイムとリンカ(特にARMアーキテクチャ向けの5l、x86-64向けの6l、x86向けの8l)における、外部リンキングと共有ライブラリのサポートを大幅に強化するものです。具体的には、スレッドローカルストレージ(TLS)の扱いを改善し、GoプログラムがCなどの非Goプログラムから呼び出し可能な共有ライブラリとして機能できるようにするための基盤を構築しています。これは、Goエコシステムにおける相互運用性と柔軟性を高める上で重要なマイルストーンとなります。

コミット

runtime.cmd/ld: Add ARM external linking and implement -shared in terms of external linking

This CL is an aggregate of 10271047, 10499043, 9733044. Descriptions of each follow:

10499043
runtime,cmd/ld: Merge TLS symbols and teach 5l about ARM TLS

This CL prepares for external linking support to ARM.

The pseudo-symbols runtime.g and runtime.m are merged into a single
runtime.tlsgm symbol. When external linking, the offset of a thread local
variable is stored at a memory location instead of being embedded into a offset
of a ldr instruction. With a single runtime.tlsgm symbol for both g and m, only
one such offset is needed.

The larger part of this CL moves TLS code from gcc compiled to internally
compiled. The TLS code now uses the modern MRC instruction, and 5l is taught
about TLS fallbacks in case the instruction is not available or appropriate.

10271047
This CL adds support for -linkmode external to 5l.

For 5l itself, use addrel to allow for D_CALL relocations to be handled by the
host linker. Of the cases listed in rsc's comment in issue 4069, only case 5 and
63 needed an update. One of the TODO: addrel cases was since replaced, and the
rest of the cases are either covered by indirection through addpool (cases with
LTO or LFROM flags) or stubs (case 74). The addpool cases are covered because
addpool emits AWORD instructions, which in turn are handled by case 11.

In the runtime, change the argv argument in the rt0* functions slightly to be a
pointer to the argv list, instead of relying on a particular location of argv.

9733044
The -shared flag to 6l outputs a shared library, implemented in Go
and callable from non-Go programs such as C.

The main part of this CL change the thread local storage model.
Go uses the fastest and least general mode, local exec. TLS data in shared
libraries normally requires at least the local dynamic mode, however, this CL
instead opts for using the initial exec mode. Initial exec mode is faster than
local dynamic mode and can be used in linux since the linker has reserved a
limited amount of TLS space for performance sensitive TLS code.

Initial exec mode requires an extra load from the GOT table to determine the
TLS offset. This penalty will not be paid if ld is not in -shared mode, since
TLS accesses will be reduced to local exec.

The elf sections .init_array and .rela.init_array are added to register the Go
runtime entry with cgo at library load time.

The "hidden" attribute is added to Cgo functions called from Go, since Go
does not generate call through the GOT table, and adding non-GOT relocations for
a global function is not supported by gcc. Cgo symbols don't need to be global
and avoiding the GOT table is also faster.

The changes to 8l are only removes code relevant to the old -shared mode where
internal linking was used.

This CL only address the low level linker work. It can be submitted by itself,
but to be useful, the runtime changes in CL 9738047 is also needed.

Design discussion at
https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/zmjXkGrEx6Q

Fixes #5590.

R=rsc
CC=golang-dev
https://golang.org/cl/12871044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/45233734e28776a6679dd8aa9f66a1d545ca8ec6

元コミット内容

上記の「コミット」セクションに記載されている内容が、このコミットの元々の内容です。これは、3つの独立した変更セット(CLs)をまとめたものです。

変更の背景

このコミットの背景には、Goプログラムのより高度な相互運用性と柔軟性への要求がありました。特に、以下の点が挙げられます。

  1. 共有ライブラリのサポート: Goで書かれたコードを、Cなどの他の言語で書かれたアプリケーションから共有ライブラリ(.so, .dll, .dylibなど)として利用できるようにすること。これにより、既存のシステムにGoの機能を組み込むことが容易になります。
  2. 外部リンキングの必要性: Goの標準リンカ(5l, 6l, 8lなど)は、これまで主に静的リンクを前提としていました。しかし、共有ライブラリを作成したり、システム提供のライブラリと連携したりする際には、外部リンカ(GCCのldなど)の機能が必要になります。特に、スレッドローカルストレージ(TLS)の扱いや、動的リンクにおけるシンボル解決には、外部リンカの協力が不可欠でした。
  3. ARMアーキテクチャへの対応: ARMはモバイルデバイスや組み込みシステムで広く利用されており、Goがこれらのプラットフォームでより強力に機能するためには、ARMアーキテクチャにおける外部リンキングと共有ライブラリのサポートが不可欠でした。
  4. TLSモデルの最適化: スレッドローカルストレージは、各スレッドが独自のデータを持つために使用されます。共有ライブラリのコンテキストでは、TLSのアクセス方法が複雑になることがありますが、パフォーマンスを維持しつつ、適切なTLSモデルを選択する必要がありました。Goはこれまで「local exec」という最も高速だが汎用性の低いTLSモデルを使用していましたが、共有ライブラリではより汎用的なモデルが必要となります。

これらの課題に対処するため、Goのリンカとランタイムに根本的な変更が加えられました。

前提知識の解説

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

1. リンカ (Linker)

リンカは、コンパイラによって生成されたオブジェクトファイル(機械語コードとデータを含む)を結合し、実行可能ファイルやライブラリを作成するプログラムです。主な役割は以下の通りです。

  • シンボル解決: 異なるオブジェクトファイルで定義された関数や変数の参照を、それらが実際に存在するメモリ上のアドレスに解決します。
  • 再配置 (Relocation): コードやデータ内のアドレス参照を、最終的なメモリレイアウトに合わせて調整します。
  • ライブラリの結合: 静的ライブラリや動的ライブラリをプログラムにリンクします。

Goには、5l (ARM), 6l (x86-64), 8l (x86) といった独自のリンカが存在します。これらはGoの特殊なランタイム要件やクロスコンパイルの容易さを考慮して設計されています。

2. 外部リンキング (External Linking)

Goのリンカが、システムにインストールされているGCCなどの外部リンカの機能を利用して最終的な実行可能ファイルやライブラリを生成するプロセスです。これにより、Goのリンカ単独では扱いにくい複雑なリンキングシナリオ(例: 共有ライブラリの作成、Cライブラリとの連携、特定のELFセクションの配置など)に対応できるようになります。

複数のプログラムで共有されるコードとデータの集合体です。プログラムの実行時にメモリにロードされ、複数のプロセスで同じライブラリのインスタンスを共有することで、メモリ使用量を削減し、ディスクスペースを節約できます。また、ライブラリの更新が容易になるという利点もあります。

4. スレッドローカルストレージ (Thread Local Storage, TLS)

各スレッドが独自のデータインスタンスを持つことを可能にするメカニズムです。グローバル変数とは異なり、TLSに格納されたデータはスレッド間で共有されず、各スレッドが独立してアクセスできます。TLSは、スレッドセーフなプログラミングや、スレッド固有の状態を管理する際に重要です。

TLSへのアクセス方法は、リンカやOSによっていくつかのモデルがあります。

  • Local Exec (ローカル実行): 最も高速なモデルで、TLS変数のオフセットがコンパイル時に固定されます。主に静的にリンクされた実行可能ファイルで使用されます。
  • Initial Exec (初期実行): 共有ライブラリで使用されるモデルの一つで、TLS変数のオフセットを決定するためにGOT (Global Offset Table) を介した間接参照が必要になります。local execよりは遅いですが、local dynamicよりは高速です。
  • Local Dynamic (ローカル動的): 最も汎用的なモデルで、TLS変数のオフセットが実行時に動的に解決されます。最も柔軟ですが、最もオーバーヘッドが大きいです。

5. ELF (Executable and Linkable Format)

Unix系システムで広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準ファイルフォーマットです。ELFファイルは、プログラムコード、データ、シンボルテーブル、再配置情報、セクションヘッダなどの要素を含みます。

  • .init_array / .fini_array セクション: ELFファイル内の特殊なセクションで、プログラムの起動時(.init_array)や終了時(.fini_array)に実行される関数ポインタのリストを格納します。共有ライブラリがロードされた際に初期化ルーチンを実行するために使用されます。

6. GOT (Global Offset Table) と PLT (Procedure Linkage Table)

動的リンクにおいて、共有ライブラリ内の関数やデータにアクセスするためのメカニズムです。

  • GOT: 共有ライブラリ内のグローバル変数や関数へのオフセットを格納するテーブルです。
  • PLT: 共有ライブラリ内の関数を呼び出す際に使用される小さなコードスタブのテーブルです。

7. Cgo

GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。Cgoを使用すると、GoとCの間の相互運用が可能になります。

技術的詳細

このコミットは、GoのリンカとランタイムにおけるTLSの扱い、外部リンキング、および共有ライブラリのサポートに関して、以下の重要な技術的変更を導入しています。

1. TLSシンボルの統合とARM TLSのサポート (CL 10499043)

  • runtime.gruntime.m の統合: Goランタイムでは、ゴルーチン(g)とマシン(m、OSスレッドに相当)の情報をスレッドローカルに保持しています。これまではruntime.gruntime.mという2つの擬似シンボルで表現されていましたが、外部リンキングを考慮し、これらをruntime.tlsgmという単一のシンボルに統合しました。これにより、TLS変数のオフセットを管理する際に、必要なオフセットが1つで済むようになり、リンカの処理が簡素化されます。
  • ARM TLSの実装: ARMアーキテクチャにおけるTLSアクセスは、MRC (Move from Coprocessor Register) 命令を使用します。この命令は、コプロセッサ(この場合はCP15)のレジスタから値を読み出すもので、TLSベースアドレスの取得に利用されます。このコミットでは、TLS関連のコードをGCCコンパイルからGoの内部コンパイルに移行し、MRC命令を直接使用するように変更しました。
  • TLSフォールバック: 古いARMプロセッサや特定の環境ではMRC命令が利用できない場合があります。そのため、runtime.read_tls_fallbackというフォールバックメカニズムが導入され、5lリンカがこのフォールバックを適切に処理するように教育されました。これにより、幅広いARMデバイスでの互換性が確保されます。
  • D_TLS リロケーションタイプ: 5lリンカの5.out.hD_TLSリロケーションタイプが定義され、R_ARM_TLS_LE32(Local Exec)とR_ARM_TLS_IE32(Initial Exec)のARM固有のTLSリロケーションタイプがサポートされました。これにより、リンカはTLSアクセスを適切に再配置できるようになります。

2. 5lにおける外部リンキングのサポート (CL 10271047)

  • -linkmode external フラグ: 5lリンカに-linkmode externalフラグが追加され、Goのリンカが最終的なリンク処理を外部リンカに委ねるモードが導入されました。これにより、Goのリンカが直接サポートしていない複雑なリンキングシナリオ(例: 共有ライブラリの作成)が可能になります。
  • D_CALL リロケーションの外部リンカへの委譲: 5lは、D_CALLリロケーション(関数呼び出しに関連する再配置)を外部リンカが処理できるようにaddrelメカニズムを使用するように変更されました。これにより、Goのリンカが直接解決できない外部シンボルへの呼び出しが、システムリンカによって適切に解決されるようになります。
  • rt0* 関数の argv 変更: ランタイムの初期化関数(rt0_linux_arm.sなど)において、argv引数の渡し方が変更されました。これまではargvが特定のメモリ位置にあることを前提としていましたが、より汎用的なポインタ渡しに変更することで、外部リンキング環境での柔軟性が向上しました。

3. 6lにおける共有ライブラリのサポートとTLSモデルの変更 (CL 9733044)

  • -shared フラグの導入: 6lリンカに-sharedフラグが導入され、Goプログラムを共有ライブラリとしてビルドできるようになりました。これにより、GoのコードをCなどの他の言語から呼び出すことが可能になります。
  • TLSモデルの「Initial Exec」への移行: 共有ライブラリでは、通常「Local Exec」モデルは使用できません。このコミットでは、Goの共有ライブラリにおけるTLSモデルを「Local Exec」から「Initial Exec」に移行しました。
    • 「Initial Exec」は「Local Dynamic」よりも高速であり、Linuxではパフォーマンス重視のTLSコードのために限られたTLS空間が予約されているため、このモデルが選択されました。
    • 「Initial Exec」モードでは、TLSオフセットを決定するためにGOT(Global Offset Table)からの追加のロードが必要になります。ただし、-sharedモードでない場合は、TLSアクセスは「Local Exec」に最適化されるため、このオーバーヘッドは発生しません。
  • .init_array セクションの追加: ELFの.init_arrayセクションが追加され、共有ライブラリがロードされる際にGoランタイムのエントリポイントをcgoに登録できるようになりました。これにより、Goランタイムの初期化が適切に行われます。
  • Cgo関数の hidden 属性: Goから呼び出されるCgo関数に__attribute__ ((visibility ("hidden")))属性が追加されました。これは、GoがGOTテーブルを介した呼び出しを生成しないため、グローバル関数に対する非GOT再配置がGCCでサポートされていない問題を回避するためです。また、シンボルを隠蔽することで、リンキングの効率も向上します。
  • 8lからの古い -shared コードの削除: 8lリンカから、内部リンキングを使用していた古い-sharedモードに関連するコードが削除されました。これは、新しい外部リンキングベースの共有ライブラリ実装への移行を反映しています。

これらの変更は、Goがより多様なシステム環境に統合され、他の言語との相互運用性を高めるための重要なステップです。特に、TLSの複雑な側面を適切に処理し、パフォーマンスと柔軟性のバランスを取ることに重点が置かれています。

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

このコミットは広範囲にわたる変更を含んでいますが、特に重要な変更箇所をいくつか挙げます。

  1. src/cmd/5l/5.out.h:

    • D_TLSリロケーションタイプの定義に// R_ARM_TLS_LE32コメントが追加され、ARM TLSリロケーションの意図が明確化されました。
  2. src/cmd/5l/asm.c:

    • elfreloc1関数にD_CALLD_TLSのリロケーション処理が追加されました。D_CALLR_ARM_CALLまたはR_ARM_JUMP24に、D_TLSflag_sharedに応じてR_ARM_TLS_IE32またはR_ARM_TLS_LE32にマッピングされます。
    • archreloc関数にLinkExternalモードでのD_CALLリロケーションの処理が追加され、外部リンカがシンボルを解決できるようにr->xsymr->xaddが設定されます。
    • asmout関数(アセンブリコード出力)のシグネチャにSym *gmsymが追加され、TLSシンボルruntime.tlsgmの情報を利用できるようになりました。
    • asmout内のケース5(分岐命令)とケース63(テーブル分岐)で、addrelを使用してD_CALLリロケーションが追加されるようになりました。
    • asmout内のTLSレジスタフェッチ(AWORD命令)の処理が大幅に変更され、runtime.tlsgmシンボルとD_TLSリロケーションタイプを使用して、外部リンキングモードでのTLSオフセットのロードが実装されました。
  3. src/cmd/5l/noop.c:

    • noop関数内でruntime.read_tls_fallbackruntime.tlsgmシンボルがルックアップされ、TLSレジスタフェッチの書き換えロジックが追加されました。特に、MRC命令が利用できない場合や外部リンキングの場合に、TLSオフセットをロードするための命令シーケンスが生成されます。
  4. src/cmd/5l/obj.c:

    • main関数でflag_sharedが設定されている場合にlinkmodeLinkExternalに設定されるようになりました。
    • tlsoffsetのコメントが../../pkg/runtime/cgo/gcc_linux_arm.cから../../pkg/runtime/rt0_*_arm.sに変更され、TLSオフセットがランタイムのスタートアップコードで利用されることが示されました。
    • runtime.read_tls_fallbackシンボルがmarkされるようになりました。
  5. src/cmd/5l/pass.c:

    • patch関数にflag_sharedが設定されている場合にinit_arrayセクションを追加するロジックが追加されました。
  6. src/cmd/6l/asm.c:

    • elfreloc1関数にD_TLSリロケーション処理が追加され、flag_sharedに応じてR_X88_64_GOTTPOFF(Initial Exec)またはR_X86_64_TPOFF32(Local Exec)にマッピングされます。
  7. src/cmd/6l/pass.c:

    • patch関数にflag_sharedが設定されている場合にinit_arrayセクションを追加するロジックが追加されました。
    • TLSアクセス(D_FS, D_GS)の変換ロジックが追加されました。flag_sharedでない場合はD_INDIR+D_FSD_INDIR+D_GSに変換され、flag_sharedの場合はMOVQ runtime.tlsgm(SB), regとそれに続くTLSアクセス命令に変換されます。
    • dostkoff関数内で、flag_sharedの場合にMOVQ runtime.tlsgm(SB), CX命令が追加され、TLSオフセットをCXレジスタにロードするようになりました。
  8. src/cmd/6l/span.c:

    • vaddr関数でflag_sharedかつSTLSBSSタイプの場合にD_TLSリロケーションが設定されるようになりました。
    • putrelv関数で、TLSアクセスに関連するリロケーションがruntime.tlsgmシンボルを参照するように変更されました。
  9. src/cmd/8l/span.c:

    • putrelv関数で、TLSアクセスに関連するリロケーションがruntime.tlsgmシンボルを参照するように変更されました。
  10. src/cmd/cgo/out.go:

    • Cgoによって生成されるラッパー関数に__attribute__ ((visibility ("hidden")))属性が追加されました。
  11. src/cmd/dist/buildruntime.c:

    • amd64アーキテクチャのランタイム定義において、get_tlsマクロがMOVQ runtime·tlsgm(SB), rを使用するように変更され、g(r)m(r)0(r)(GS*1)8(r)(GS*1)を使用するように変更されました。これは、Initial Exec TLSモデルを反映しています。
  12. src/cmd/ld/data.c:

    • dodata関数で、.data.rel.roセクションの代わりに.init_arrayセクションが追加されるようになりました。
  13. src/cmd/ld/elf.c:

    • doelf関数で、flag_sharedの場合に.init_arrayおよび関連するリロケーションセクション(.rela.init_arrayまたは.rel.init_array)がELFファイルに追加されるようになりました。
    • ELFヘッダのタイプが、linkmode == LinkExternalの場合にET_RELに、それ以外の場合にET_EXECに設定されるようになりました。以前はflag_sharedの場合にET_DYNに設定されていましたが、これは削除されました。
  14. src/cmd/ld/lib.c:

    • INITENTRYの生成ロジックが変更され、flag_sharedの場合に_rt0_%s_%s_libというエントリポイントが使用されるようになりました。

これらの変更は、Goのリンカが外部リンキングと共有ライブラリをサポートするために必要な、低レベルのシンボル処理、リロケーション、ELFセクション管理、およびランタイムの初期化メカニズムの調整を示しています。

コアとなるコードの解説

このコミットの核となる変更は、GoのリンカがTLSアクセスを処理し、共有ライブラリを生成する方法を根本的に変えた点にあります。

TLSアクセスの抽象化と最適化

以前のGoでは、runtime.gruntime.mという擬似シンボルを介してゴルーチンとマシンのTLSデータにアクセスしていました。これは主に静的リンクされたバイナリ向けに最適化された「Local Exec」TLSモデルを使用していました。しかし、共有ライブラリでは、TLS変数のアドレスがコンパイル時に完全に固定されないため、より柔軟なアクセス方法が必要です。

このコミットでは、runtime.gruntime.mruntime.tlsgmという単一のシンボルに統合しました。これにより、TLSオフセットの管理が簡素化されます。

特にARMアーキテクチャでは、MRC命令(コプロセッサレジスタからの読み出し)を使用してTLSベースアドレスを取得するように変更されました。これは、ARMv6以降のモダンなARMプロセッサで推奨されるTLSアクセス方法です。古いARMプロセッサとの互換性のために、runtime.read_tls_fallbackというフォールバック関数も導入され、リンカがこれを適切に処理するように設定されました。

x86-64アーキテクチャでは、src/cmd/dist/buildruntime.cの変更が重要です。get_tlsマクロがMOVQ runtime·tlsgm(SB), rを使用するように変更され、g(r)m(r)0(r)(GS*1)8(r)(GS*1)を使用するように変更されました。これは、TLSベースアドレスをruntime.tlsgmシンボルからロードし、そのレジスタをベースとしてTLSデータにアクセスする「Initial Exec」TLSモデルを実装しています。このモデルは、GOTを介した間接参照を必要としますが、Local Dynamicモデルよりも高速です。

外部リンキングと共有ライブラリの実現

5l6lリンカに-linkmode externalフラグと-sharedフラグが導入されたことが、共有ライブラリ実現の鍵です。

  • -linkmode external: このフラグは、Goのリンカが最終的なリンクステップをシステムリンカ(例: ld)に委ねることを意味します。これにより、Goのリンカが直接サポートしていない複雑なリロケーションタイプや、システムライブラリとの連携が可能になります。例えば、D_CALLリロケーションが外部リンカに委譲されることで、Goのコードから外部の共有ライブラリ関数を呼び出す際のシンボル解決が適切に行われます。

  • -shared: このフラグは、GoのリンカがGoプログラムを共有ライブラリとしてビルドすることを指示します。共有ライブラリは、他のプログラム(Cプログラムなど)からロードされ、その中のGo関数を呼び出すことができます。

    • 共有ライブラリのロード時にGoランタイムを初期化するために、ELFの.init_arrayセクションが利用されます。このセクションには、ライブラリロード時に実行される関数へのポインタが格納されており、Goランタイムの初期化ルーチンがここに登録されます。
    • Cgoによって生成されるGoとCの間のラッパー関数には__attribute__ ((visibility ("hidden")))属性が追加されました。これは、これらのシンボルが外部から直接参照される必要がなく、リンカがGOTを介した呼び出しを生成しないGoの内部的な呼び出し規約と整合性を保つためです。また、シンボルを隠蔽することで、リンキングの効率も向上します。

これらの変更により、Goは単なるスタンドアロンの実行可能ファイルを生成するだけでなく、他の言語のエコシステムとより深く統合できる柔軟な共有ライブラリを生成する能力を獲得しました。これは、Goの適用範囲を広げ、既存のシステムへの組み込みを容易にする上で非常に重要な進歩です。

関連リンク

参考にした情報源リンク