[インデックス 1856] ファイルの概要
このコミットは、Go言語のリンカである6l
のソースコードに対する変更です。具体的には、src/cmd/6l/asm.c
とsrc/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オペレーティングシステムのツールチェイン(特にアセンブラとリンカ)をベースにしていました。この背景には、以下の具体的な問題意識があったと考えられます。
- 関数名の重複と混乱:
vputl
とllputl
という2つの関数が、実質的に同じ目的(64ビット整数をリトルエンディアン形式で書き込む)のために存在していました。このような重複は、コードの理解を妨げ、将来的なバグの原因となる可能性があります。リンカのような低レベルのツールでは、正確性と一貫性が極めて重要です。 - アーキテクチャ固有のパスのハードコーディング:
rt0
(ランタイムエントリポイント)ファイルのパスが、特定のアーキテクチャ(この場合は6
、すなわちamd64)にハードコードされていました。これは、Goが複数のアーキテクチャをサポートする上で、ビルドスクリプトやツールチェインの柔軟性を損なう要因となります。thechar
のような汎用的な記号を使用することで、異なるアーキテクチャへの移植が容易になります。 - コードの保守性向上: 重複するコードやハードコードされた値は、バグ修正や新機能追加の際に、複数の箇所を変更する必要が生じ、エラーを引き起こしやすくなります。このコミットは、これらの問題を解決し、コードベース全体の保守性を高めることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の前提知識が役立ちます。
- 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つの技術的な変更を含んでいます。
-
関数名の正規化と重複排除:
src/cmd/6l/asm.c
において、llput
関数がvput
に、llputl
関数がvputl
にそれぞれリネームされました。llput
とllputl
は、それぞれ64ビット整数(vlong
)を書き込むための関数ですが、lput
(32ビット整数を書き込む)との関連性や、vlong
を扱うという点でvput
やvputl
という名前の方がより適切であると判断されたと考えられます。- 特に、
vputl
はlputl
(32ビット整数をリトルエンディアンで書き込む)を2回呼び出すことで64ビット整数をリトルエンディアンで書き込む実装になっており、llputl
と全く同じ機能を提供していました。この重複が解消され、vputl
が唯一の標準的な関数として残されました。これにより、コードベースの冗長性が減り、将来的なメンテナンスが容易になります。
-
アーキテクチャ依存パスの抽象化:
src/cmd/6l/obj.c
のmain
関数内で、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
と全く同じ実装を持つ関数が別途存在していましたが、このコミットでllputl
がvputl
にリネームされたことで、重複が解消され、元の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"
となり、最後の.6
がthechar
変数に置き換えられました。 - この変更により、リンカは実行時に
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ファイルフォーマットの仕様 (特にプログラムヘッダとセクションヘッダの構造)
- エンディアンネスに関する一般的な情報