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

[インデックス 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つの領域で技術的な修正を行っています。

  1. 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が処理されるようになったことを示唆しています。
  2. リンカパスの実行順序の修正:

    • 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/ldsrc/liblink ディレクトリ)
  • Go言語のリンカの内部動作に関する技術記事やブログポスト (例: "Go's Linker" by Russ Cox など)
  • スレッドローカルストレージに関する一般的なコンピュータサイエンスの資料