[インデックス 17939] ファイルの概要
このコミットは、Goランタイムにおけるruntime.goarm
変数の宣言方法を修正するものです。具体的には、リンカがこの変数の唯一の真の宣言を提供する責任を持つべきであるという原則に基づき、重複する非extern
宣言を削除しています。これにより、リンカの役割を明確にし、シンボル定義の一貫性を保つことを目的としています。
コミット
- コミットハッシュ:
4230044bb867f80469076e3407ef368a93ca8d56
- 作者: Russ Cox rsc@golang.org
- コミット日時: Mon Dec 9 19:35:07 2013 -0500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4230044bb867f80469076e3407ef368a93ca8d56
元コミット内容
runtime: remove non-extern decls of runtime.goarm
The linker is in charge of providing the one true declaration.
R=golang-dev, dave, r
CC=golang-dev
https://golang.org/cl/39560043
変更の背景
この変更の背景には、runtime.goarm
というGoランタイム内部で使用される変数の宣言が、複数の場所で行われていたという問題があります。特に、アセンブリコード(asm_arm.s
)とCコード(os_linux_arm.c
)の両方で、この変数が実体を持つ形で宣言されていました。
プログラムのビルドプロセスにおいて、リンカ(Goの場合、5l
などのツールチェーンの一部)は、異なるコンパイル単位(オブジェクトファイル)で定義されたシンボル(変数や関数など)を結合し、最終的な実行可能ファイルを生成する役割を担います。この際、同じシンボルが複数の場所で実体を持つ形で定義されていると、「多重定義エラー(multiple definition error)」が発生する可能性があります。たとえエラーにならなくても、どの定義が「真」であるかという曖昧さが生じ、予期せぬ動作やデバッグの困難さを招くことがあります。
runtime.goarm
は、Goランタイムが動作するARMプロセッサのバージョン(例: ARMv5, ARMv6, ARMv7)を示すために使用される重要な変数です。この値に基づいて、ランタイムは特定のARM固有の機能(例えば、VFP (Vector Floating Point) の動作モード)を調整します。この変数の値は、リンカによって設定されることが意図されていました。
したがって、このコミットの目的は、runtime.goarm
の宣言を一元化し、リンカがその唯一の定義を提供するという設計原則を徹底することにありました。これにより、コードの明確性が向上し、将来的なメンテナンスやデバッグが容易になります。
前提知識の解説
このコミットを理解するためには、以下の概念について知っておく必要があります。
-
runtime.goarm
: Goランタイム内部で使用される変数で、Goプログラムが実行されているARMプロセッサのアーキテクチャバージョンを示します。例えば、runtime.goarm
が6
であればARMv6、7
であればARMv7といった具合です。この値は、ランタイムがARM固有の最適化や機能(例: 浮動小数点演算ユニット (VFP) の設定)を適切に適用するために利用されます。この値は通常、ビルド時にリンカによって設定されます。 -
ARMアーキテクチャのバージョン: ARMプロセッサは、ARMv5、ARMv6、ARMv7などの複数のアーキテクチャバージョンを持っています。これらのバージョン間には、命令セット、キャッシュの動作、浮動小数点ユニットの有無や機能などに違いがあります。Goランタイムは、これらの違いを吸収し、最適なパフォーマンスと互換性を提供するために、実行環境のARMアーキテクチャバージョンを認識する必要があります。
-
リンカの役割: リンカは、コンパイラによって生成された複数のオブジェクトファイル(
.o
ファイルなど)とライブラリを結合し、単一の実行可能ファイルまたは共有ライブラリを生成するツールです。リンカの主要な役割は以下の通りです。- シンボル解決: あるオブジェクトファイルで参照されているシンボル(変数や関数)が、別のオブジェクトファイルで定義されている場合、リンカはその参照を実際の定義に解決します。
- 再配置: オブジェクトファイル内のアドレスは相対的なものであるため、リンカはこれらを最終的なメモリレイアウトに合わせて絶対アドレスに調整します。
- セクションの結合: コード、データ、読み取り専用データなどの異なるセクションを結合し、最終的な実行可能ファイルの構造を決定します。
- 多重定義の検出: 同じシンボルが複数の場所で定義されている場合、リンカは通常、エラーを報告します。
-
GLOBL
ディレクティブ (アセンブリ): アセンブリ言語において、GLOBL
(またはGLOBAL
)ディレクティブは、それに続くシンボルがグローバルシンボルであり、他のオブジェクトファイルから参照可能であることをリンカに指示します。これは、そのシンボルがメモリ上に実体を持ち、外部からアクセスできることを意味します。 -
extern
キーワード (C言語): C言語において、extern
キーワードは、変数が現在のコンパイル単位(ソースファイル)の外部で定義されていることをコンパイラに伝えます。extern
宣言は、変数の型と名前を宣言しますが、その変数にメモリを割り当てません。これは、リンカが最終的にその変数の定義を見つけて解決することを期待していることを意味します。extern
を使用することで、多重定義エラーを回避しつつ、複数のソースファイルから同じグローバル変数にアクセスできるようになります。
このコミットは、runtime.goarm
の定義がGLOBL
と非extern
宣言によって複数箇所で行われていた状態から、リンカが唯一の定義を提供する形に修正することで、シンボル解決の曖昧さを排除し、ビルドプロセスの一貫性を高めています。
技術的詳細
このコミットの技術的な核心は、runtime.goarm
という変数の「真の定義」をリンカに一元化することにあります。変更前は、この変数がアセンブリコードとCコードの両方で実体を持つ形で宣言されており、リンカがその値を設定する際に潜在的な競合や曖昧さがありました。
具体的には、以下の3つのファイルが変更されています。
-
src/cmd/5l/obj.c
: これはGoのARMリンカ(5l
)のソースコードの一部です。リンカは、runtime.goarm
というシンボルを検索し、その値を設定する責任を負っています。- 変更前:
s->dupok = 1;
dupok
は「duplicate OK」の略で、このシンボルが重複して定義されていても許容するというフラグです。これは、リンカがruntime.goarm
の複数の定義を許容していたことを示唆しており、リンカが「唯一の真の宣言」を提供するという原則に反していました。 - 変更後:
s->type = SRODATA;
SRODATA
は「Read-Only Data Section」を意味します。この変更により、リンカはruntime.goarm
を読み取り専用データセクションに配置されるべきシンボルとして扱い、その型を明示的に設定します。これにより、リンカがこのシンボルの定義を厳密に管理し、他の場所での実体を持つ宣言を排除する意図が強化されます。リンカがこのシンボルを定義し、その値を埋め込む唯一のエンティティであることを明確にしています。
- 変更前:
-
src/pkg/runtime/asm_arm.s
: これはGoランタイムのARMアーキテクチャ向けアセンブリコードです。- 変更前:
GLOBL runtime·goarm(SB), $4
GLOBL
ディレクティブは、runtime·goarm
がグローバルシンボルであり、4バイトのメモリを割り当てて実体を持つことを宣言していました。これは、アセンブリコードがruntime.goarm
の定義を提供していたことを意味します。 - 変更後: この行が削除されました。
これにより、アセンブリコードは
runtime.goarm
の定義を提供しなくなります。アセンブリコードは、この変数を参照するだけで、その定義はリンカに任されることになります。
- 変更前:
-
src/pkg/runtime/os_linux_arm.c
: これはGoランタイムのLinux ARM環境固有のCコードです。- 変更前:
uint8 runtime·goarm; // set by 5l
この行は、runtime·goarm
がuint8
型の変数として宣言されており、メモリが割り当てられることを意味します。コメントには「set by 5l」とありますが、これはリンカによって値が設定されることを示唆しつつも、Cコンパイラにとっては実体を持つ定義として扱われていました。 - 変更後:
extern uint8 runtime·goarm; // set by 5l
extern
キーワードが追加されました。これにより、runtime·goarm
は現在のCファイルでは定義されず、他の場所(この場合はリンカ)で定義されていることがコンパイラに伝えられます。このファイルはruntime.goarm
を参照するだけで、そのメモリ割り当てや初期化は行いません。
- 変更前:
これらの変更を総合すると、runtime.goarm
の定義は、アセンブリコードとCコードから削除され、リンカ(5l
)がその唯一の定義と値の設定を行う責任を持つように修正されました。これにより、シンボルの多重定義の問題が解消され、ビルドシステム全体でのruntime.goarm
の扱いが一貫性を持つようになります。これは、Goのビルドシステムがシンボルをどのように管理し、リンカがそのプロセスにおいて中心的な役割を果たすかを示す良い例です。
コアとなるコードの変更箇所
src/cmd/5l/obj.c
--- a/src/cmd/5l/obj.c
+++ b/src/cmd/5l/obj.c
@@ -95,6 +95,6 @@ archinit(void)
// embed goarm to runtime.goarm
s = linklookup(ctxt, "runtime.goarm", 0);
- s->dupok = 1;
+ s->type = SRODATA;
adduint8(ctxt, s, goarm);
}
src/pkg/runtime/asm_arm.s
--- a/src/pkg/runtime/asm_arm.s
+++ b/src/pkg/runtime/asm_arm.s
@@ -89,8 +89,6 @@ TEXT runtime·breakpoint(SB),NOSPLIT,$0-0
WORD $0xe1200071 // BKPT 0x0001
RET
-GLOBL runtime·goarm(SB), $4
-
TEXT runtime·asminit(SB),NOSPLIT,$0-0
// disable runfast (flush-to-zero) mode of vfp if runtime.goarm > 5
MOVW runtime·goarm(SB), R11
src/pkg/runtime/os_linux_arm.c
--- a/src/pkg/runtime/os_linux_arm.c
+++ b/src/pkg/runtime/os_linux_arm.c
@@ -16,7 +16,7 @@
static uint32 runtime·randomNumber;
uint8 runtime·armArch = 6; // we default to ARMv6
uint32 runtime·hwcap; // set by setup_auxv
-uint8 runtime·goarm; // set by 5l
+extern uint8 runtime·goarm; // set by 5l
void
runtime·checkgoarm(void)
コアとなるコードの解説
src/cmd/5l/obj.c
の変更
- 変更前:
s->dupok = 1;
s
はリンカが扱うシンボルを表す構造体へのポインタです。dupok
は「duplicate OK」の略で、このフラグが1
に設定されていると、リンカはruntime.goarm
シンボルが複数の場所で定義されていてもエラーとせず、重複を許容していました。これは、リンカがこのシンボルの「唯一の真の定義」を強制するのではなく、複数の定義の中から一つを選択するか、あるいは未定義動作を引き起こす可能性がありました。 - 変更後:
s->type = SRODATA;
dupok
フラグの削除と同時に、シンボルs
のタイプをSRODATA
(Read-Only Data Section)に明示的に設定しています。これにより、リンカはruntime.goarm
を読み取り専用データとして扱い、その定義を自身が管理することを明確にしています。この変更は、リンカがruntime.goarm
の定義を排他的に所有し、他の場所での実体を持つ宣言を排除するという意図を強化します。
src/pkg/runtime/asm_arm.s
の変更
- 変更前:
GLOBL runtime·goarm(SB), $4
このアセンブリディレクティブは、runtime·goarm
というシンボルをグローバルとして宣言し、4バイトのメモリ領域を割り当てていました。これは、アセンブリコードがruntime·goarm
の定義(実体)を提供していたことを意味します。 - 変更後: この行が削除されました。
この削除により、アセンブリコードは
runtime·goarm
の定義を提供しなくなります。アセンブリコードは引き続きMOVW runtime·goarm(SB), R11
のようにこのシンボルを参照しますが、その定義はリンカによって提供されることを期待するようになります。これにより、アセンブリコードとリンカの間でruntime·goarm
の定義に関する責任が明確に分離されます。
src/pkg/runtime/os_linux_arm.c
の変更
- 変更前:
uint8 runtime·goarm; // set by 5l
このC言語の宣言は、runtime·goarm
がuint8
型の変数として定義され、メモリが割り当てられることを意味します。コメントには「set by 5l」とありますが、Cコンパイラにとってはこれは実体を持つ定義であり、リンカが別の場所で同じシンボルを定義しようとすると、多重定義エラーの潜在的な原因となります。 - 変更後:
extern uint8 runtime·goarm; // set by 5l
extern
キーワードが追加されました。extern
は、変数が現在のコンパイル単位(このCファイル)の外部で定義されていることをコンパイラに伝えます。これにより、このファイルはruntime·goarm
を参照するだけで、そのメモリ割り当てや初期化は行いません。runtime·goarm
の実際の定義はリンカによって提供されることが明確になります。
これらの変更は、runtime.goarm
という単一の変数の定義を、Goのビルドシステム全体で一貫性のある方法で管理するためのものです。リンカがこの変数の「唯一の真の定義」を提供し、他のコンパイル単位はextern
宣言やシンボル参照を通じてその定義を利用するという、より堅牢な設計パターンに移行しています。これにより、ビルド時の多重定義エラーのリスクが低減され、コードの保守性が向上します。
関連リンク
- Go CL (Code Review) ページ: https://golang.org/cl/39560043
参考にした情報源リンク
- Go言語のソースコード(上記コミットのファイル群)
- C言語の
extern
キーワードに関する一般的な知識 - アセンブリ言語の
GLOBL
ディレクティブに関する一般的な知識 - リンカの動作に関する一般的な知識
- ARMアーキテクチャに関する一般的な知識