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

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

このコミットは、Go言語のリンカである6lのソースコードに対する変更です。具体的には、src/cmd/6l/asm.csrc/cmd/6l/obj.cの2つのファイルが修正されています。主な目的は、コードの重複を排除し、可読性と保守性を向上させることです。

コミット

commit 997b6f9d89067c5d48c072ebcdcf288ba4f9e1d2
Author: Russ Cox <rsc@golang.org>
Date:   Fri Mar 20 14:22:46 2009 -0700

    don't need two names for the same function (vputl and llputl).
    also use thechar, to make copy/paste easier.
    
    R=ken
    OCL=26583
    CL=26588

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

https://github.com/golang/go/commit/997b6f9d89067c5d48c072ebcdcf288ba4f9e1d2

元コミット内容

    don't need two names for the same function (vputl and llputl).
    also use thechar, to make copy/paste easier.
    
    R=ken
    OCL=26583
    CL=26588

変更の背景

このコミットは、Go言語の初期開発段階におけるコードベースの整理と改善の一環として行われました。当時のGoツールチェインは、Plan 9オペレーティングシステムのツールチェイン(特にアセンブラとリンカ)をベースにしていました。この背景には、以下の具体的な問題意識があったと考えられます。

  1. 関数名の重複と混乱: vputlllputlという2つの関数が、実質的に同じ目的(64ビット整数をリトルエンディアン形式で書き込む)のために存在していました。このような重複は、コードの理解を妨げ、将来的なバグの原因となる可能性があります。リンカのような低レベルのツールでは、正確性と一貫性が極めて重要です。
  2. アーキテクチャ固有のパスのハードコーディング: rt0(ランタイムエントリポイント)ファイルのパスが、特定のアーキテクチャ(この場合は6、すなわちamd64)にハードコードされていました。これは、Goが複数のアーキテクチャをサポートする上で、ビルドスクリプトやツールチェインの柔軟性を損なう要因となります。thecharのような汎用的な記号を使用することで、異なるアーキテクチャへの移植が容易になります。
  3. コードの保守性向上: 重複するコードやハードコードされた値は、バグ修正や新機能追加の際に、複数の箇所を変更する必要が生じ、エラーを引き起こしやすくなります。このコミットは、これらの問題を解決し、コードベース全体の保守性を高めることを目的としています。

前提知識の解説

このコミットを理解するためには、以下の前提知識が役立ちます。

  • Go言語のツールチェイン: Go言語は、独自のコンパイラ、アセンブラ、リンカ、デバッガなどのツールチェインを持っています。初期のGoツールチェインは、Plan 9のツールチェイン(8c, 6c, 5cなどのコンパイラ、8a, 6a, 5aなどのアセンブラ、8l, 6l, 5lなどのリンカ)をベースにしていました。
    • 6l: このコミットで変更されている6lは、x86-64(AMD64)アーキテクチャ向けのGoリンカです。オブジェクトファイル(.6拡張子を持つことが多い)を結合し、実行可能ファイルを生成する役割を担います。
  • rt0ファイル: rt0(runtime 0)は、Goプログラムの実行が開始される最初のエントリポイントとなるアセンブリコードファイルです。C言語におけるcrt0(C runtime 0)に相当します。オペレーティングシステムから制御が渡された後、Goランタイムの初期化(スタックの設定、レジスタのクリア、ガベージコレクタの準備など)を行い、最終的にGoのmain関数を呼び出します。アーキテクチャやOSごとに異なるrt0ファイルが存在します(例: rt0_linux_amd64.s)。
  • vlong: Go言語のリンカやアセンブラの文脈で使われるvlongは、通常64ビットの整数型を指します。C言語のlong longやGoのint64に相当します。
  • リトルエンディアン (Little-endian): 複数バイトで構成されるデータをメモリに格納する際のバイト順序の一つです。最下位バイト(Least Significant Byte, LSB)が最も小さいアドレスに格納され、最上位バイト(Most Significant Byte, MSB)が最も大きいアドレスに格納されます。x86およびx86-64アーキテクチャはリトルエンディアンを採用しています。
  • thechar: Plan 9および初期のGoツールチェインで使われた慣習的な変数名です。これは、現在のビルドターゲットのアーキテクチャを示す文字(例: 6はamd64、8はarm64、5はarmなど)を保持します。これにより、アーキテクチャ固有のファイルパスなどを動的に構築できます。
  • PADDR: 物理アドレス(Physical Address)を意味するマクロまたは関数で、仮想アドレスから物理アドレスへの変換を行う際に使用されることがあります。リンカが実行可能ファイルを生成する際に、メモリレイアウトを決定する上で重要な概念です。
  • HEADR: ヘッダサイズを意味する定数で、実行可能ファイルのヘッダ部分のサイズを示します。

技術的詳細

このコミットは、主に以下の2つの技術的な変更を含んでいます。

  1. 関数名の正規化と重複排除:

    • src/cmd/6l/asm.cにおいて、llput関数がvputに、llputl関数がvputlにそれぞれリネームされました。
    • llputllputlは、それぞれ64ビット整数(vlong)を書き込むための関数ですが、lput(32ビット整数を書き込む)との関連性や、vlongを扱うという点でvputvputlという名前の方がより適切であると判断されたと考えられます。
    • 特に、vputllputl(32ビット整数をリトルエンディアンで書き込む)を2回呼び出すことで64ビット整数をリトルエンディアンで書き込む実装になっており、llputlと全く同じ機能を提供していました。この重複が解消され、vputlが唯一の標準的な関数として残されました。これにより、コードベースの冗長性が減り、将来的なメンテナンスが容易になります。
  2. アーキテクチャ依存パスの抽象化:

    • src/cmd/6l/obj.cmain関数内で、rt0ファイルのパスを構築する際に、ハードコードされていたアーキテクチャ文字'6'thechar変数に置き換えられました。
    • 変更前: sprintf(a, "%s/lib/rt0_%s_%s.6", goroot, goarch, goos);
    • 変更後: sprintf(a, "%s/lib/rt0_%s_%s.%c", goroot, goarch, goos, thechar);
    • この変更により、リンカが参照するrt0ファイルのパスが、ビルド時に決定されるthecharの値に基づいて動的に生成されるようになります。これにより、Goツールチェインが異なるアーキテクチャ(例: ARM、PowerPCなど)をサポートする際に、コードの変更なしに適切なrt0ファイルをロードできるようになり、クロスコンパイルの柔軟性が向上します。
    • また、machstack(va+HEADR)machstack(entryvalue())に変更されています。これは、スタックの初期化に関連する変更で、entryvalue()が実行可能ファイルのエントリポイントの仮想アドレスを返す関数であることから、より正確かつ汎用的な方法でスタックのベースアドレスを設定するように修正されたと考えられます。

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

src/cmd/6l/asm.c

--- a/src/cmd/6l/asm.c
+++ b/src/cmd/6l/asm.c
@@ -82,7 +82,7 @@ lput(int32 l)
 }
 
 void
-llput(vlong v)
+vput(vlong v)
 {
 	lput(v>>32);
 	lput(v);
@@ -98,7 +98,7 @@ lputl(int32 l)
 }
 
 void
-llputl(vlong v)
+vputl(vlong v)
 {
 	lputl(v);
 	lputl(v>>32);
@@ -287,7 +287,7 @@ asmb(void)
 		tlput(PADDR(vl));		/* va of entry */
 		tlput(spsize);			/* sp offsets */
 		tlput(lcsize);			/* line offsets */
-		tllput(vl);			/* va of entry */
+		tvput(vl);			/* va of entry */
 		break;
 	case 3:	/* plan9 */
 		magic = 4*26*26+7;
@@ -402,7 +402,7 @@ asmb(void)
 			1);			/* flag - zero fill */
 
 		tmachdylink();
-		tmachstack(va+HEADR);
+		tmachstack(entryvalue());
 
 		if (!debug['s']) {
 			tmachseg("__SYMDAT",
@@ -427,12 +427,12 @@ asmb(void)
 		twputl(2);			/* type = EXEC */
 		twputl(62);			/* machine = AMD64 */
 		tlputl(1L);			/* version = CURRENT */
-		tllputl(entryvalue());		/* entry vaddr */
-		tllputl(64L);			/* offset to first phdr */
+		tvputl(entryvalue());		/* entry vaddr */
+		tvputl(64L);			/* offset to first phdr */
 		tnp = 3;
 		if(!debug['s'])
 			tnp++;
-		tllputl(64L+56*np);		/* offset to first shdr */
+		tvputl(64L+56*np);		/* offset to first shdr */
 		tlputl(0L);			/* processor specific flags */
 		twputl(64);			/* Ehdr size */
 		twputl(56);			/* Phdr size */
@@ -780,13 +780,6 @@ rnd(vlong v, vlong r)
 	return v;
 }
 
-void
-vputl(vlong v)
-{
-	lputl(v);
-	lputl(v>>32);
-}
-
 void
 tmachseg(char *name, vlong vaddr, vlong vsize, vlong foff, vlong fsize,
 	uint32 prot1, uint32 prot2, uint32 nsect, uint32 flag)
@@ -941,12 +934,12 @@ linuxphdr(int type, int flags, vlong foff,
 
 	tlputl(type);			/* text - type = PT_LOAD */
 	tlputl(flags);			/* text - flags = PF_X+PF_R */
-	tllputl(foff);			/* file offset */
-	tllputl(vaddr);			/* vaddr */
-	tllputl(paddr);			/* paddr */
-	tllputl(filesize);		/* file size */
-	tllputl(memsize);		/* memory size */
-	tllputl(align);			/* alignment */
+	tvputl(foff);			/* file offset */
+	tvputl(vaddr);			/* vaddr */
+	tvputl(paddr);			/* paddr */
+	tvputl(filesize);		/* file size */
+	tvputl(memsize);		/* memory size */
+	tvputl(align);			/* alignment */
 }
 
 void
@@ -955,14 +948,14 @@ linuxshdr(char *name, uint32 type, vlong flags, vlong addr, vlong off,
 {
 	tlputl(stroffset);
 	tlputl(type);
-	tllputl(flags);
-	tllputl(addr);
-	tllputl(off);
-	tllputl(size);
+	tvputl(flags);
+	tvputl(addr);
+	tvputl(off);
+	tvputl(size);
 	tlputl(link);
 	tlputl(info);
-	tllputl(align);
-	tllputl(entsize);
+	tvputl(align);
+	tvputl(entsize);
 
 	if(name != nil)
 		stroffset += strlen(name)+1;

src/cmd/6l/obj.c

--- a/src/cmd/6l/obj.c
+++ b/src/cmd/6l/obj.c
@@ -355,7 +355,7 @@ main(int argc, char *argv[])
 
 	if(!debug['l']) {
 		ta = mal(strlen(goroot)+strlen(goarch)+strlen(goos)+20);
-		tsprint(a, "%s/lib/rt0_%s_%s.6", goroot, goarch, goos);
+		tsprint(a, "%s/lib/rt0_%s_%s.%c", goroot, goarch, goos, thechar);
 		tobjfile(a);
 	}
 

コアとなるコードの解説

src/cmd/6l/asm.cの変更点

  • 関数名の変更:
    • llput(vlong v)vput(vlong v)にリネームされました。この関数は、64ビット整数vを2つの32ビット整数として(上位32ビット、下位32ビットの順で)書き込むものです。
    • llputl(vlong v)vputl(vlong v)にリネームされました。この関数は、64ビット整数vをリトルエンディアン形式で(下位32ビット、上位32ビットの順で)書き込むものです。
  • 重複関数の削除:
    • 以前はvputlという名前でllputlと全く同じ実装を持つ関数が別途存在していましたが、このコミットでllputlvputlにリネームされたことで、重複が解消され、元のvputl関数は削除されました。これにより、vputlが64ビット整数をリトルエンディアンで書き込む唯一の標準的な関数となりました。
  • machstack呼び出しの修正:
    • tmachstack(va+HEADR)tmachstack(entryvalue())に変更されました。
      • va+HEADRは、実行可能ファイルの仮想アドレスにヘッダサイズを加えたもので、プログラムの開始アドレスを指す意図があったと考えられます。
      • entryvalue()は、リンカが決定したプログラムのエントリポイントの仮想アドレスを返す関数です。
      • この変更は、スタックの初期化をより正確かつ堅牢に行うために、ハードコードされたオフセット計算ではなく、リンカが持つ正確なエントリポイント情報を使用するように修正されたことを示唆しています。これにより、異なる実行可能ファイル形式やロードアドレスの変更にも柔軟に対応できるようになります。
  • llputlからvputlへの呼び出し箇所の変更:
    • asmb関数内の様々な箇所(特にELFヘッダやプログラムヘッダ、セクションヘッダの書き込み部分)で、tllputおよびtllputlの呼び出しが、それぞれtvputおよびtvputlに置き換えられました。これは、関数名変更に伴う一貫性のための修正です。

src/cmd/6l/obj.cの変更点

  • rt0ファイルパスの動的生成:
    • main関数内で、rt0(ランタイムエントリポイント)ファイルのパスを構築するtsprint関数のフォーマット文字列が変更されました。
    • 変更前は"%s/lib/rt0_%s_%s.6"と、アーキテクチャを示す.6がハードコードされていました。
    • 変更後は"%s/lib/rt0_%s_%s.%c"となり、最後の.6thechar変数に置き換えられました。
    • この変更により、リンカは実行時にthecharの値(例えば、amd64の場合は'6'、arm64の場合は'8'など)を使用して、適切なrt0ファイルを動的にロードできるようになります。これにより、Goツールチェインのクロスコンパイル能力とアーキテクチャ非依存性が向上します。

これらの変更は、Go言語のツールチェインが初期段階から、より汎用的で保守性の高い設計へと進化していく過程を示しています。

関連リンク

  • Go言語の初期のツールチェインに関する議論: https://go.dev/doc/go1.0#toolchain
  • Plan 9のツールチェインに関する情報: https://9p.io/sys/doc/compiler.html
  • Goのrt0に関する解説(例: rt0_linux_amd64.sのコード): Goのソースコードリポジトリ内でsrc/runtime/rt0_*.sを検索すると見つかります。

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコードリポジトリ (GitHub)
  • Plan 9のドキュメント
  • ELFファイルフォーマットの仕様 (特にプログラムヘッダとセクションヘッダの構造)
  • エンディアンネスに関する一般的な情報