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

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

このコミットは、Goコンパイラ(6cおよび6g)に-picフラグを追加し、大規模モデルのコード生成をサポートするための変更を導入します。これにより、RIP相対アドレッシングを使用できないアセンブラ命令を回避し、6lリンカでの-sharedモードのサポートを可能にします。

コミット

  • コミットハッシュ: fe14ee52ccf89fa02366a06fe892a7fcf135e214
  • Author: Elias Naur elias.naur@gmail.com
  • Date: Fri Feb 1 08:35:33 2013 -0800

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

https://github.com/golang/go/commit/fe14ee52ccf89fa02366a06fe892a7fcf135e214

元コミット内容

cmd/6c, cmd/6g: add flag to support large-model code generation

Added the -pic flag to 6c and 6g to avoid assembler instructions that
cannot use RIP-relative adressing. This is needed to support the -shared mode
in 6l.

See also:
https://golang.org/cl/6926049
https://golang.org/cl/6822078

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

変更の背景

この変更の主な背景は、Goプログラムが共有ライブラリとして機能できるようにすること、特に6lリンカの-sharedモードをサポートすることにあります。共有ライブラリは、複数のプログラムでコードを共有することでメモリ使用量を削減し、起動時間を短縮する利点があります。

しかし、x86-64アーキテクチャでは、デフォルトのコード生成がRIP相対アドレッシング(命令ポインタ相対アドレッシング)に依存しています。RIP相対アドレッシングは、命令ポインタ(RIPレジスタ)からのオフセットを使用してデータやコードにアクセスする効率的な方法です。これは、実行時にコードがメモリ内のどこにロードされても正しく機能するため、位置独立コード(PIC: Position-Independent Code)の生成に役立ちます。

しかし、RIP相対アドレッシングにはアドレス空間の制限があります。特に、非常に大きなプログラムや、複数の共有ライブラリがロードされるような複雑なシナリオでは、RIP相対アドレッシングで到達できる範囲を超えるデータやコードにアクセスする必要が生じる可能性があります。このような場合、RIP相対アドレッシングに依存しない、より汎用的なアドレッシングモードが必要となります。

このコミットは、GoコンパイラがRIP相対アドレッシングに依存しないコードを生成できるようにすることで、この問題を解決しようとしています。これにより、Goプログラムが大規模なメモリモデルで動作したり、共有ライブラリとして適切に機能したりすることが可能になります。

前提知識の解説

1. Goコンパイラとリンカ (6c, 6g, 6l)

Goの初期のツールチェーンでは、特定のアーキテクチャ向けのコンパイラとリンカに数字と文字の組み合わせが使われていました。

  • 6c: x86-64 (AMD64) アーキテクチャ向けのCコンパイラ。Goのランタイムや標準ライブラリの一部はCで書かれており、これらをコンパイルするために使用されます。
  • 6g: x86-64 (AMD64) アーキテクチャ向けのGoコンパイラ。Go言語のソースコードをアセンブリコードに変換します。
  • 6l: x86-64 (AMD64) アーキテクチャ向けのGoリンカ。コンパイルされたオブジェクトファイルやライブラリを結合して実行可能ファイルや共有ライブラリを生成します。

これらのツールは、Goのビルドプロセスにおいて重要な役割を担っています。

2. RIP相対アドレッシング (RIP-relative addressing)

x86-64アーキテクチャにおけるアドレッシングモードの一つで、命令ポインタ(RIPレジスタ)の現在値からの相対オフセットを使用してメモリ上のデータやコードにアクセスします。

特徴:

  • 位置独立性: コードがメモリ上のどこにロードされても、RIPからの相対オフセットは変わらないため、コードを再配置することなく実行できます。これは、共有ライブラリ(DLLや.soファイル)をビルドする際に非常に重要です。
  • 効率性: 命令が短くなり、実行速度が向上する場合があります。
  • アドレス空間の制限: RIPからのオフセットは通常32ビット符号付き整数で表現されるため、RIPから約±2GBの範囲にしかアクセスできません。大規模なプログラムや、非常に多くのグローバルデータを持つプログラムでは、この制限が問題となることがあります。

3. 位置独立コード (PIC: Position-Independent Code)

メモリ上の任意のアドレスにロードされても正しく実行できるコードのことです。共有ライブラリは通常、PICとしてコンパイルされます。PICは、プログラムがロードされるたびにアドレスを修正する「再配置」の必要がないため、メモリ効率が良く、複数のプロセスで同じコードを共有できます。RIP相対アドレッシングはPICを生成するための一般的な手法ですが、前述の通りアドレス空間の制限があります。

4. 大規模モデル (Large-model code generation)

RIP相対アドレッシングの2GBの制限を超えるアドレス空間にアクセスできるコード生成モデルを指します。これは、特に大規模なアプリケーションや、多くのグローバル変数を持つアプリケーションで必要となります。大規模モデルでは、RIP相対アドレッシングに代わる、より汎用的なアドレッシングモード(例: 絶対アドレッシングや、ベースレジスタとインデックスレジスタを組み合わせたアドレッシング)が使用されます。これにより、コードのサイズがわずかに増加したり、実行速度がわずかに低下したりする可能性がありますが、より広いアドレス空間にアクセスできるようになります。

5. -sharedモード (in 6l)

Goリンカ6lにおける-sharedモードは、Goプログラムを共有ライブラリとしてビルドするためのオプションです。このモードを使用すると、生成されたGoコードが他のプログラムから動的にリンクされ、実行時にロードされるようになります。共有ライブラリとして機能するためには、そのコードが位置独立である必要があります。

技術的詳細

このコミットは、Goコンパイラ(6c6g)に-picフラグを導入し、このフラグが有効な場合にRIP相対アドレッシングを回避するコードを生成するように変更します。これにより、大規模モデルのコード生成が可能になり、6lリンカでの-sharedモードがサポートされます。

具体的な変更点は以下の通りです。

  1. flag_largemodel変数の導入:

    • src/cmd/cc/cc.hsrc/cmd/gc/go.hEXTERN int flag_largemodel;が追加され、コンパイラ全体で大規模モデルのフラグの状態を共有できるようになります。
    • src/cmd/cc/lex.csrc/cmd/gc/lex.cmain関数内で、コマンドライン引数として-largemodelフラグがパースされ、flag_largemodel変数が設定されるようになります。これは、thechar == '6'(x86-64アーキテクチャの場合)にのみ適用されます。
  2. sgen.cにおけるONAMEノードのaddable値の調整:

    • src/cmd/6c/sgen.cxcom関数内で、ONAME(名前付き変数や関数など)のノードのaddable値が変更されます。
    • flag_largemodelが有効な場合、n->addable9に設定されます。これは、RIP相対アドレッシングが使用できないことをコンパイラに示唆し、より汎用的なアドレッシングモードを選択させるためのヒントとなります。通常モードでは10です。
  3. 6g/cgen.cにおけるデータ文字列アドレッシングの変更:

    • src/cmd/6g/cgen.cagenr関数内で、データ文字列へのアドレッシング方法が変更されます。
    • flag_largemodelが有効な場合、ALEAQ命令(アドレスをロードする命令)の後にAADDQ命令が追加され、RIP相対アドレッシングに依存しない形でアドレスを計算するようになります。これにより、データがRIPレジスタから遠く離れた場所にあってもアクセスできるようになります。
  4. 6g/ggen.cにおける関数呼び出しの引数プッシュの変更:

    • src/cmd/6g/ggen.cginscall関数内で、関数呼び出しの引数をスタックにプッシュする際の処理が変更されます。
    • flag_largemodelが有効な場合、引数fを直接APUSHQするのではなく、一時レジスタr1gmoveでコピーしてからAPUSHQするようになります。これは、RIP相対アドレッシングが使えない状況で、fが直接プッシュできないような複雑なアドレッシングモードを持つ場合に、レジスタを介することで汎用的なプッシュを可能にするためと考えられます。
  5. 6g/gsubr.cにおけるismem関数の変更:

    • src/cmd/6g/gsubr.cismem関数内で、OADDR(アドレス演算子)の場合にflag_largemodelが有効であれば1を返すようになります。これは、大規模モデルの場合にアドレスがメモリとして扱われるべきであることを示唆し、コンパイラが適切なコードを生成するためのヒントとなります。
  6. make.bashにおけるコンパイラフラグの追加:

    • src/make.bashスクリプトにGO_CCFLAGS変数が導入され、コンパイラ(5c/6c/8c)に追加の引数を渡せるようになります。
    • go_bootstrap installコマンドに-ccflags "$GO_CCFLAGS"が追加され、ビルド時に-largemodelフラグをコンパイラに渡せるようになります。

これらの変更により、Goコンパイラは-picフラグが指定された場合に、RIP相対アドレッシングに依存しない、より汎用的なコードを生成するようになります。これにより、Goプログラムを共有ライブラリとしてビルドしたり、非常に大きなアドレス空間を持つ環境で実行したりすることが可能になります。

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

このコミットでは、以下のファイルが変更されています。

  • src/cmd/6c/sgen.c: ONAMEノードのaddable値の調整。
  • src/cmd/6g/cgen.c: データ文字列アドレッシングの変更。
  • src/cmd/6g/ggen.c: 関数呼び出しの引数プッシュの変更。
  • src/cmd/6g/gsubr.c: ismem関数の変更。
  • src/cmd/cc/cc.h: flag_largemodel変数の宣言追加。
  • src/cmd/cc/lex.c: -largemodelフラグのパース処理追加。
  • src/cmd/gc/go.h: flag_largemodel変数の宣言追加。
  • src/cmd/gc/lex.c: -largemodelフラグのパース処理追加。
  • src/make.bash: GO_CCFLAGSの導入とgo_bootstrap installコマンドへの-ccflags引数追加。

コアとなるコードの解説

src/cmd/6c/sgen.c

--- a/src/cmd/6c/sgen.c
+++ b/src/cmd/6c/sgen.c
@@ -126,7 +126,10 @@ xcom(Node *n)
 		break;
 
 	case ONAME:
-		n->addable = 10;
+		if(flag_largemodel)
+			n->addable = 9;
+		else
+			n->addable = 10;
 		if(n->class == CPARAM || n->class == CAUTO)
 			n->addable = 11;
 		break;

ONAMEノード(変数や関数名など)のaddableプロパティは、そのノードがどのようにアドレス指定可能であるかを示すヒントです。flag_largemodelが有効な場合、addable9に設定されます。これは、RIP相対アドレッシングが利用できないことをコンパイラに伝え、より汎用的なアドレッシングモード(例: 絶対アドレス指定)を使用するように促します。

src/cmd/6g/cgen.c

--- a/src/cmd/6g/cgen.c
+++ b/src/cmd/6g/cgen.c
@@ -737,8 +737,12 @@ agenr(Node *n, Node *a, Node *res)
 			regalloc(&n3, types[tptr], res);
 			p1 = gins(ALEAQ, N, &n3);
 			datastring(nl->val.u.sval->s, nl->val.u.sval->len, &p1->from);
-			p1->from.scale = 1;
-			p1->from.index = n2.val.u.reg;
+			if(flag_largemodel) {
+				gins(AADDQ, &n2, &n3);
+			} else {
+				p1->from.scale = 1;
+				p1->from.index = n2.val.u.reg;
+			}
 			goto indexdone;
 		}

データ文字列のアドレスを計算する部分です。flag_largemodelが有効な場合、ALEAQ命令(アドレスをレジスタにロード)の後にAADDQ命令(加算)が追加されます。これは、RIP相対アドレッシングを使わずに、ベースレジスタとオフセットを明示的に加算することで、データ文字列の絶対アドレスを計算するための変更です。これにより、データがRIPレジスタから遠く離れていてもアクセス可能になります。

src/cmd/6g/ggen.c

--- a/src/cmd/6g/ggen.c
+++ b/src/cmd/6g/ggen.c
@@ -59,6 +59,7 @@ ginscall(Node *f, int proc)
 {
 	Prog *p;
 	Node reg, con;
+	Node r1;
 
 	switch(proc) {
 	default:
@@ -76,7 +77,14 @@ ginscall(Node *f, int proc)
 	case 1:	// call in new proc (go)
 	case 2:	// deferred call (defer)
 		nodreg(&reg, types[TINT64], D_CX);
-		gins(APUSHQ, f, N);
+		if(flag_largemodel) {
+			regalloc(&r1, f->type, f);
+			gmove(f, &r1);
+			gins(APUSHQ, &r1, N);
+			regfree(&r1);
+		} else {
+			gins(APUSHQ, f, N);
+		}
 		nodconst(&con, types[TINT32], argsize(f->type));
 		gins(APUSHQ, &con, N);
 		if(proc == 1)

関数呼び出しの引数をスタックにプッシュする部分です。flag_largemodelが有効な場合、引数fを直接APUSHQ(スタックにプッシュ)するのではなく、一時レジスタr1gmoveでコピーしてからAPUSHQします。これは、fがRIP相対アドレッシングに依存するような複雑なアドレッシングモードを持つ場合に、レジスタを介することで汎用的なプッシュを可能にするための変更です。

src/cmd/6g/gsubr.c

--- a/src/cmd/6g/gsubr.c
+++ b/src/cmd/6g/gsubr.c
@@ -554,6 +554,10 @@ ismem(Node *n)
 	case ONAME:
 	case OPARAM:
 		return 1;
+	case OADDR:
+		if(flag_largemodel)
+			return 1;
+		break;
 	}
 	return 0;
 }

ismem関数は、ノードがメモリとして扱われるべきかどうかを判断します。OADDR(アドレス演算子)の場合にflag_largemodelが有効であれば1を返すようになります。これは、大規模モデルの場合にアドレスがメモリとして扱われるべきであることを示唆し、コンパイラが適切なコードを生成するためのヒントとなります。

src/cmd/cc/lex.c および src/cmd/gc/lex.c

--- a/src/cmd/cc/lex.c
+++ b/src/cmd/cc/lex.c
@@ -174,6 +174,8 @@ main(int argc, char *argv[])
 	flagcount("t", "debug code generation", &debug['t']);
 	flagcount("w", "enable warnings", &debug['w']);
 	flagcount("v", "increase debug verbosity", &debug['v']);	
+	if(thechar == '6')
+		flagcount("largemodel", "generate code that assumes a large memory model", &flag_largemodel);
 	
 	flagparse(&argc, &argv, usage);

コンパイラのメイン関数で、コマンドライン引数-largemodelをパースし、flag_largemodel変数を設定する部分です。これにより、ユーザーはコンパイル時に大規模モデルのコード生成を明示的に有効にできます。

src/make.bash

--- a/src/make.bash
+++ b/src/make.bash
@@ -23,6 +23,9 @@
 # GO_LDFLAGS: Additional 5l/6l/8l arguments to use when
 # building the commands.
 #
+# GO_CCFLAGS: Additional 5c/6c/8c arguments to use when
+# building.
+#
 # CGO_ENABLED: Controls cgo usage during the build. Set it to 1
 # to include all cgo related files, .c and .go file with "cgo"
 # build directive, in the build. Set it to 0 to ignore them.
@@ -129,12 +132,12 @@ echo
 if [ "$GOHOSTARCH" != "$GOARCH" -o "$GOHOSTOS" != "$GOOS" ]; then
 	echo "# Building packages and commands for host, $GOHOSTOS/$GOHOSTARCH."
 	GOOS=$GOHOSTOS GOARCH=$GOHOSTARCH \
-		"$GOTOOLDIR"/go_bootstrap install -gcflags "$GO_GCFLAGS" -ldflags "$GO_LDFLAGS" -v std
+		"$GOTOOLDIR"/go_bootstrap install -ccflags "$GO_CCFLAGS" -gcflags "$GO_GCFLAGS" -ldflags "$GO_LDFLAGS" -v std
 	echo
 fi
 
 echo "# Building packages and commands for $GOOS/$GOARCH."
-"$GOTOOLDIR"/go_bootstrap install -gcflags "$GO_GCFLAGS" -ldflags "$GO_LDFLAGS" -v std
+"$GOTOOLDIR"/go_bootstrap install $GO_FLAGS -ccflags "$GO_CCFLAGS" -gcflags "$GO_GCFLAGS" -ldflags "$GO_LDFLAGS" -v std
 echo
 
 rm -f "$GOTOOLDIR"/go_bootstrap

GoのビルドスクリプトにGO_CCFLAGS変数が追加され、go_bootstrap installコマンドに-ccflags引数が渡されるようになります。これにより、ビルドシステム全体でコンパイラに-largemodelなどのフラグを渡すことが可能になり、大規模モデルのサポートがビルドプロセスに統合されます。

関連リンク

参考にした情報源リンク