[インデックス 17930] ファイルの概要
このコミットは、Go言語のリンカである cmd/6l
における Linux/amd64 環境での問題を修正するものです。具体的には、スレッドローカルストレージ (TLS) の取り扱いと、リンカの最適化パスの順序に関する修正が含まれています。
コミット
commit 1eac128d648da34431850e9393c2404921105073
Author: Russ Cox <rsc@golang.org>
Date: Mon Dec 9 09:58:35 2013 -0800
cmd/6l: fix linux/amd64
TBR=iant
CC=golang-dev
https://golang.org/cl/39530043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1eac128d648da34431850e9393c2404921105073
元コミット内容
cmd/6l: fix linux/amd64
変更の背景
このコミットは、Go言語のリンカ 6l
が Linux/amd64 環境で正しく動作しない問題を解決するために導入されました。特に、Goランタイムがスレッドローカルストレージ (TLS) を介して現在のゴルーチン (g) ポインタにアクセスする方法に関連するバグが原因でした。
Goランタイムは、現在のゴルーチンに関する情報(g
ポインタ)を効率的に取得するために、TLSを利用することがあります。しかし、リンカがこのTLSアクセスを正しく処理しない場合、生成されるバイナリがクラッシュしたり、予期せぬ動作をしたりする可能性があります。
また、リンカの処理順序、特に patch()
と deadcode()
の呼び出し順序も問題の原因となっていました。deadcode()
は到達不能なコードを削除する最適化パスであり、patch()
はコードの修正やシンボルの解決を行うパスです。これらのパスの順序が不適切だと、patch()
が必要とする情報が deadcode()
によって削除されてしまったり、patch()
の結果が deadcode()
に正しく反映されなかったりする可能性があります。
このコミットは、これらの問題を解決し、Linux/amd64 環境でのGoプログラムの安定性と正確性を向上させることを目的としています。
前提知識の解説
- Go リンカ (
cmd/6l
): Go言語のコンパイラツールチェーンの一部であり、コンパイルされたオブジェクトファイル (.o
ファイル) を結合して実行可能なバイナリを生成するプログラムです。6l
は AMD64 (x86-64) アーキテクチャ向けのリンカを指します。 - スレッドローカルストレージ (TLS): 各スレッドが独自のデータコピーを持つことができるメモリ領域です。Goランタイムでは、現在のゴルーチン (goroutine) のポインタ (
g
ポインタ) をTLSに格納し、高速にアクセスするために利用することがあります。これにより、システムコールを介さずにg
ポインタを取得でき、パフォーマンスが向上します。 runtime.tlsgm
: Goランタイム内部で使用されるシンボルで、TLSを介してg
ポインタを取得するためのメカニズムに関連しています。- リンカパス (
patch
,deadcode
):patch()
: リンカのパスの一つで、コード内の参照を解決したり、アドレスを修正したりする役割を担います。例えば、関数呼び出しのアドレスを実際のメモリ上のアドレスに置き換えたりします。deadcode()
: リンカの最適化パスの一つで、プログラムから到達不能なコード(デッドコード)を特定し、最終的なバイナリから削除します。これにより、バイナリサイズが削減され、実行効率が向上する可能性があります。
LSym
(Linker Symbol): リンカが扱うシンボル(関数名、変数名など)を表すデータ構造です。Prog
(Program Instruction): リンカが処理する個々の機械語命令を表すデータ構造です。Link
(Linker Context): リンカの現在の状態や設定、シンボルテーブルなどを保持するコンテキスト構造体です。
技術的詳細
このコミットは、主に以下の2つの領域で技術的な修正を行っています。
-
runtime.tlsgm
シンボルの取り扱いの一貫性向上:- 以前のコードでは、
runtime.tlsgm
シンボルがprogedit
関数内でローカル変数gmsym
として宣言され、初期化されていました。しかし、この初期化が常に適切に行われるとは限らず、またaddstacksplit
関数など他の場所でもruntime.tlsgm
が必要とされる場合に、再度linklookup
を呼び出す必要がありました。 - 今回の修正では、
Link
コンテキスト構造体 (ctxt
) にgmsym
フィールド (ctxt->gmsym
) を導入し、runtime.tlsgm
シンボルをリンカのグローバルなコンテキストで一度だけルックアップしてキャッシュするように変更されました。これにより、runtime.tlsgm
へのアクセスが一貫性を持つようになり、リンカの各パスでシンボルを再ルックアップするオーバーヘッドが削減されます。 - 特に、
progedit
関数内でop runtime.tlsgm(SB), reg
のような命令をNOP
(No Operation) に変換する処理において、ctxt->gmsym
を使用することで、TLS関連の命令の最適化がより堅牢になりました。 - また、以前はコメントアウトされていたTLS関連のコードブロックが削除されました。これは、
runtime.tlsgm
の取り扱いが改善されたことで、そのコードが不要になったか、あるいはより洗練された方法でTLSが処理されるようになったことを示唆しています。
- 以前のコードでは、
-
リンカパスの実行順序の修正:
src/cmd/ld/pobj.c
において、deadcode()
とpatch()
の呼び出し順序が変更されました。- 変更前:
deadcode(); patch();
- 変更後:
patch(); deadcode();
- この変更は非常に重要です。
patch()
はシンボルの解決やアドレスの修正を行うため、deadcode()
が実行される前に完了している必要があります。もしdeadcode()
が先に実行されてしまうと、patch()
が必要とするシンボルやコードが誤って削除されてしまい、リンカが正しく動作しなくなる可能性があります。 patch()
が先に実行されることで、すべてのシンボル参照が解決され、コードが適切に修正された状態になってから、deadcode()
が到達不能なコードを安全に削除できるようになります。これにより、リンカの正確性と安定性が向上します。
これらの変更は、GoリンカがLinux/amd64環境でTLSを正しく処理し、最適化パスが適切な順序で実行されることを保証することで、生成されるGoバイナリの信頼性を高めます。
コアとなるコードの変更箇所
src/cmd/ld/pobj.c
--- a/src/cmd/ld/pobj.c
+++ b/src/cmd/ld/pobj.c
@@ -186,8 +186,8 @@ main(int argc, char *argv[])
mark(linklookup(ctxt, "runtime.read_tls_fallback", 0));
}
- deadcode();
patch();
+ deadcode();
follow();
dostkoff();
paramspace = "SP"; /* (FP) now (SP) on output */
src/liblink/obj6.c
--- a/src/liblink/obj6.c
+++ b/src/liblink/obj6.c
@@ -225,9 +225,9 @@ static void
progedit(Link *ctxt, Prog *p)
{
Prog *q;
- LSym *gmsym;
-
- gmsym = nil; // TODO
+
+ if(ctxt->gmsym == nil)
+ ctxt->gmsym = linklookup(ctxt, "runtime.tlsgm", 0);
if(ctxt->headtype == Hwindows) {
// Windows
@@ -286,16 +286,14 @@ progedit(Link *ctxt, Prog *p)
// op runtime.tlsgm(SB), reg
// to
// NOP
- if(gmsym != nil && p->from.sym == gmsym) {
+ if(ctxt->gmsym != nil && p->from.sym == ctxt->gmsym) {
p->as = ANOP;
p->from.type = D_NONE;
p->to.type = D_NONE;
p->from.sym = nil;
p->to.sym = nil;
- return;
}
} else {
- /*
// Convert TLS reads of the form
// op n(GS), reg
// to
@@ -311,10 +311,9 @@ progedit(Link *ctxt, Prog *p)
q->from.offset = p->from.offset;
p->as = AMOVQ;
p->from.type = D_EXTERN;
- p->from.sym = gmsym;
+ p->from.sym = ctxt->gmsym;
p->from.offset = 0;
}
- */
}
}
@@ -359,8 +356,9 @@ addstacksplit(Link *ctxt, LSym *cursym)
uint32 i;
vlong textstksiz, textarg;
- if(ctxt->gmsym == nil) {
+ if(ctxt->gmsym == nil)
ctxt->gmsym = linklookup(ctxt, "runtime.tlsgm", 0);
+ if(ctxt->symmorestack[0] == nil) {
if(nelem(morename) > nelem(ctxt->symmorestack))
sysfatal("Link.symmorestack needs at least %d elements", nelem(morename));
for(i=0; i<nelem(morename); i++)\
コアとなるコードの解説
src/cmd/ld/pobj.c
の変更
deadcode();
とpatch();
の呼び出し順序が入れ替わっています。patch()
は、リンカがシンボル参照を解決し、コード内のアドレスを修正する重要なパスです。例えば、関数呼び出しのターゲットアドレスを決定したり、グローバル変数のアドレスを埋め込んだりします。deadcode()
は、プログラムから到達不能なコードを削除する最適化パスです。- この変更により、まず
patch()
が実行され、すべてのシンボル参照が解決され、コードが適切に修正された状態になってから、deadcode()
が到達不能なコードを安全に削除できるようになります。これにより、リンカが誤って必要なコードを削除してしまうリスクが低減され、生成されるバイナリの正確性が保証されます。
src/liblink/obj6.c
の変更
-
progedit
関数内のgmsym
の変更:- 以前は
LSym *gmsym; gmsym = nil; // TODO
とローカル変数として宣言されていたgmsym
が削除され、代わりにリンカのコンテキスト構造体Link *ctxt
のメンバーであるctxt->gmsym
が使用されるようになりました。 if(ctxt->gmsym == nil) ctxt->gmsym = linklookup(ctxt, "runtime.tlsgm", 0);
という行がprogedit
の冒頭に追加され、runtime.tlsgm
シンボルがまだルックアップされていない場合に一度だけルックアップしてctxt->gmsym
にキャッシュするようになりました。これにより、runtime.tlsgm
シンボルへのアクセスが一貫性を持つようになります。if(gmsym != nil && p->from.sym == gmsym)
の条件がif(ctxt->gmsym != nil && p->from.sym == ctxt->gmsym)
に変更され、runtime.tlsgm
関連の命令をNOP
に変換するロジックがctxt->gmsym
を参照するように修正されました。- コメントアウトされていたTLS関連のコードブロックが削除されました。これは、
ctxt->gmsym
を使用する新しいアプローチが、以前の複雑なTLS処理を不要にしたことを示しています。
- 以前は
-
addstacksplit
関数内のgmsym
の変更:addstacksplit
関数も同様に、if(ctxt->gmsym == nil) ctxt->gmsym = linklookup(ctxt, "runtime.tlsgm", 0);
という行が追加され、runtime.tlsgm
シンボルがリンカコンテキストにキャッシュされるようになりました。if(ctxt->symmorestack[0] == nil)
という新しい条件ブロックが追加されています。これは、スタックスプリット(Goのゴルーチンスタックの動的な拡張)に関連するシンボルがまだ初期化されていない場合に、それらをルックアップしてキャッシュするためのものです。これにより、スタックスプリットの処理もより効率的かつ堅牢になります。
これらの変更は、Goリンカが runtime.tlsgm
シンボルをより効率的かつ一貫性のある方法で管理し、TLS関連の命令を正しく最適化することを可能にします。また、リンカのパスの順序を修正することで、生成されるバイナリの正確性と安定性を向上させています。
関連リンク
- Go言語のリンカに関するドキュメント (Goの公式ドキュメントやソースコード内のコメントを参照すると良いでしょう)
- Go言語のランタイムとゴルーチンに関するドキュメント
- スレッドローカルストレージ (TLS) に関する一般的な情報
参考にした情報源リンク
- golang.org/cl/39530043 (Go Gerrit Change-List)
- Go言語のソースコード (特に
src/cmd/ld
とsrc/liblink
ディレクトリ) - Go言語のリンカの内部動作に関する技術記事やブログポスト (例: "Go's Linker" by Russ Cox など)
- スレッドローカルストレージに関する一般的なコンピュータサイエンスの資料