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

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

このコミットは、Go言語のリンカにおける重要な構造変更と、既存のバイナリとの後方互換性に関する修正を含んでいます。具体的には、Go言語に特化したリンカのコード(go.c)を、各アーキテクチャ固有のリンカ(6l, 8lなど)から共通の../ld/go.cに移動し、これを各リンカがインクルードする形に変更しています。これにより、コードの重複を排除し、Goリンカの共通ロジックを一元管理できるようになります。また、8.out.hおよび6.out.hファイルにおける定数の定義順序を調整し、Plan 9の既存バージョンとの後方互換性を確保しています。これは、古い.8.6形式のオブジェクトファイルが無効になるのを防ぐための措置です。

コミット

commit b87e3e8b7f37a97c383ae5bbfadb401ec6fc243c
Author: Russ Cox <rsc@golang.org>
Date:   Tue Mar 31 00:20:07 2009 -0700

    * move go-specific loader code
    into gc directory, where it gets included as ../gc/ldbody
    this is similar to the assemblers including ../cc/lexbody
    and ../cc/macbody.
    
    * hook go-specific loader code into 8l.
    
    * make current 8.out.h and 6.out.h backward compatible
    with plan 9's versions.  i had added some constants in
    the middle of enums and have now moved them to the end.
    this keeps us from invalidating old .8 and .6 files.
    not sure how much it really matters, but easy to do.
    
    R=r
    DELTA=1314  (667 added, 623 deleted, 24 changed)
    OCL=26938
    CL=26941

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

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

元コミット内容

Go言語に特化したローダーコードをgcディレクトリに移動し、../gc/ldbodyとしてインクルードされるように変更しました。これは、アセンブラが../cc/lexbody../cc/macbodyをインクルードするのと同様のパターンです。 また、Go言語に特化したローダーコードを8lリンカに組み込みました。 現在の8.out.h6.out.hをPlan 9のバージョンと後方互換性を持つように修正しました。これは、enumの途中に定数を追加したことで、古い.8および.6ファイルを無効にしてしまう可能性があったため、それらの定数をenumの最後に移動することで対応しました。これにより、古いファイルとの互換性が維持されます。

変更の背景

このコミットには大きく分けて二つの背景があります。

  1. Go言語リンカの共通化とコードの整理: 初期のGo言語のツールチェインでは、各アーキテクチャ(例: 6lはamd64、8lは386)ごとにリンカが存在し、それぞれがGo言語固有のリンカロジックを持っていました。しかし、Go言語の機能が進化するにつれて、これらのリンカ間で共通のロジックが増えてきました。例えば、Goの型情報(gotypestringsgotypesigs)の処理、デッドコード削除、パッケージデータのロードなどは、アーキテクチャに依存しない共通の処理です。これらの共通ロジックが各リンカに分散していると、コードの重複が生じ、メンテナンスが困難になります。このコミットは、これらの共通ロジックをsrc/cmd/ld/go.cという新しい共通ファイルに集約し、各リンカがこれをインクルードする形にすることで、コードの再利用性と保守性を向上させることを目的としています。これは、Goのアセンブラが../cc/lexbody../cc/macbodyといった共通のコードをインクルードする既存のパターンを踏襲したものです。

  2. Plan 9との後方互換性の維持: Go言語のツールチェインは、Plan 9というオペレーティングシステムの設計思想とツールから大きな影響を受けています。6l8lといったリンカも、Plan 9のリンカの命名規則や一部の構造を継承しています。8.out.h6.out.hのようなヘッダファイルは、リンカがオブジェクトファイルやライブラリファイルを解釈するために必要な定数や構造体の定義を含んでいます。このコミット以前に、これらのヘッダファイルに新しい定数が追加された際、それが既存のenumの途中に挿入されてしまいました。これにより、定数の値が変更され、古いバージョンのリンカで生成された.8.6形式のオブジェクトファイルが正しく解釈できなくなる可能性がありました。このコミットでは、新しい定数をenumの末尾に移動することで、既存の定数の値を変更せず、古いオブジェクトファイルとの後方互換性を維持しています。これは、Go言語の進化の過程で、既存のツールやファイル形式との互換性を慎重に扱う必要があったことを示しています。

前提知識の解説

このコミットを理解するためには、以下の概念について理解しておく必要があります。

  • Go言語ツールチェイン: Go言語のプログラムをビルドするための一連のツール群です。主要なものとして、コンパイラ(gc)、アセンブラ(6a, 8aなど)、リンカ(6l, 8lなど)があります。
  • リンカ (6l, 8l, ld): コンパイラやアセンブラによって生成されたオブジェクトファイルやライブラリファイルを結合し、実行可能なバイナリを生成するツールです。6lはamd64アーキテクチャ用、8lは386アーキテクチャ用を指します。ldは、このコミットで共通のGo言語リンカコードを格納するために新設されたディレクトリ名であり、一般的なリンカを指す場合もあります。
  • Plan 9: ベル研究所で開発された分散オペレーティングシステムです。Go言語の設計者の一部はPlan 9の開発にも携わっており、Go言語のツールチェインや一部の設計思想にその影響が見られます。特に、Goのコンパイラやリンカの命名規則(例: 6g, 8g, 6l, 8l)はPlan 9の慣習に由来します。
  • out.hファイル: リンカがオブジェクトファイルやライブラリファイルを処理する際に使用する、命令コード、データ型、シンボルなどの定数や構造体の定義が含まれるヘッダファイルです。アーキテクチャごとに異なる場合があります(例: 6.out.h, 8.out.h)。
  • enum: C言語における列挙型です。一連の関連する定数に名前を付けるために使用されます。
  • D_SBIG: このコミットで8.out.hに追加された定数で、リンカが大きなデータを処理する際のデータタイプを示します。Go言語の文字列や型情報など、サイズが大きいデータを効率的に扱うために導入されたと考えられます。
  • シンボル (Sym): プログラム内の変数、関数、型などの名前付きエンティティを表すデータ構造です。リンカはシンボルテーブルを管理し、これらのエンティティのアドレス解決を行います。
    • STEXT: テキストセクション(コード)のシンボル。
    • SDATA: データセクション(初期化されたデータ)のシンボル。
    • SBSS: BSSセクション(初期化されていないデータ)のシンボル。
    • SXREF: 未解決の外部参照シンボル。
    • Sxxx: シンボルが破棄されたことを示す。
  • プログラム命令 (Prog): リンカが処理する個々の命令やデータ定義を表すデータ構造です。
  • Biobuf: バッファリングされたI/O操作を行うための構造体。ファイルからの読み込みなどに使用されます。
  • パッケージデータ (pkgdata): Go言語のコンパイル済みパッケージに含まれるメタデータで、型情報、エクスポートされたシンボル、メソッド情報などが含まれます。リンカはこれを読み込み、リンク時に利用します。
  • definetypestrings: Goの型情報を文字列として集約し、バイナリに埋め込むための関数です。Goのreflectパッケージなどがこの情報を使用します。
  • definetypesigs: Goの型シグネチャ(関数の引数や戻り値の型など)を集約し、バイナリに埋め込むための関数です。これもreflectパッケージなどで利用されます。
  • デッドコード削除 (deadcode): 実行されないコードや参照されないデータを最終的なバイナリから取り除く最適化手法です。これにより、バイナリサイズを削減し、起動時間を短縮できます。

技術的詳細

Go言語リンカコードの共通化

このコミットの最も大きな変更点は、Go言語に特化したリンカコードがsrc/cmd/6l/go.cからsrc/cmd/ld/go.cに移動され、各リンカ(6l, 8l)がこの共通ファイルをインクルードする形式になったことです。

  • src/cmd/6l/go.cの削除とsrc/cmd/ld/go.cの新規作成: src/cmd/6l/go.cの内容がほぼそのままsrc/cmd/ld/go.cに移動されました。これにより、6lリンカはgo.cファイルを直接コンパイルするのではなく、#include "../ld/go.c"というプリプロセッサディレクティブを通じて共通コードを取り込むようになります。同様の変更が8lリンカにも適用されています。
  • Makefileの変更: src/cmd/6l/Makefilesrc/cmd/8l/Makefilego.o: ../ld/go.cという行が追加されています。これは、go.oオブジェクトファイルが../ld/go.cからビルドされることを示しており、共通コードのインクルードパスを明示しています。
  • 共通コードの内容: src/cmd/ld/go.cには、Go言語のリンカが共通して行う処理がまとめられています。これには、以下の主要な機能が含まれます。
    • パッケージデータのロード (ldpkg, loadpkgdata, parsepkgdata): コンパイル済みのGoパッケージファイル(.aファイルなど)から型情報やエクスポートされたシンボルなどのメタデータを読み込み、リンカの内部データ構造に格納します。これにより、異なるパッケージ間で型の一貫性をチェックしたり、リフレクションに必要な情報を収集したりできます。
    • 型情報の定義 (definetypestrings, definetypesigs): Goの型システムが実行時に利用する型情報(gotypestrings)や型シグネチャ(gotypesigs)を、リンカが最終的なバイナリに埋め込む処理を行います。これらはGoのreflectパッケージが動的に型情報を取得するために不可欠です。
    • デッドコード削除 (deadcode, mark, marktext, markdata, sweeplist): 実行時に到達不可能なコードや参照されないデータを特定し、最終的なバイナリから削除する処理です。これにより、バイナリサイズを最適化し、不要なコードのロードを防ぎます。このコミットでは、deadcode関数内のsweeplistの呼び出しがコメントアウトされていますが、これは一時的な変更か、別の場所で処理されるようになったことを示唆しています。

8.out.hおよび6.out.hの後方互換性修正

このコミットでは、8.out.h6.out.hファイル内のenum定義が変更されています。

  • 定数定義順序の変更: 8.out.hでは、ACMPXCHGB, ACMPXCHGL, ACMPXCHGWといった命令コードの定数が、ALASTの前に移動されています。同様に、D_SBIGD_CONST2のデータタイプ定数も、D_INDIRの後に移動されています。 元のコードでは、これらの新しい定数が既存のenumの途中に挿入されていたため、それ以降の定数の値がずれてしまい、古いオブジェクトファイルとの互換性が失われる可能性がありました。新しい定数をenumの末尾に移動することで、既存の定数の値は変更されず、古いオブジェクトファイルが引き続き正しく解釈されるようになります。
  • D_SBIGの導入と利用: D_SBIGは、リンカが大きなデータブロックを扱うための新しいデータタイプ定数です。コミットメッセージにもあるように、Goの文字列や型情報など、100バイト単位で分割してリンカに渡されるような大きなデータを効率的に処理するために導入されました。definetypestrings関数内で、Prog構造体のto.typeフィールドにD_SBIGを設定し、to.sbigフィールドにデータへのポインタを設定することで、リンカがこの大きなデータを適切に処理できるようになります。
  • D_INDIRの範囲チェックの修正: src/cmd/8l/list.csrc/cmd/8l/span.cでは、D_INDIRに関連するアドレスタイプのチェックロジックが修正されています。i >= D_INDIRという条件がi >= D_INDIR && i < 2*D_INDIRに変更されています。これは、D_SBIGD_INDIR + D_INDIRとして定義されたため、D_INDIRを基準とした範囲チェックが正しく機能するように調整されたものです。

その他の変更点

  • src/cmd/8l/l.hの変更: Adr構造体にu0sbigフィールドが追加され、sbigマクロが定義されています。これはD_SBIGタイプのアドレスを扱うために必要です。 Prog構造体にdlinkフィールドが追加され、Sym構造体にdupok, reachable, text, dataフィールドが追加されています。これらは主にデッドコード削除のロジックをサポートするためのものです。 Symtypeを示すenumSxxxが追加され、シンボルが破棄された状態を示すために使用されます。 newdatanewtext関数のプロトタイプが追加され、リンカが新しいデータセクションやテキストセクションを生成する際のヘルパー関数として利用されます。
  • src/cmd/8l/obj.cの変更: main関数内でdefinetypestrings(), definetypesigs(), deadcode()が呼び出されるようになりました。これにより、リンカの実行フローの中でGo固有の処理が適切に実行されるようになります。 ldobj関数内でldpkgの呼び出しがコメントアウトから有効化されています。これは、パッケージデータのロードがリンカの主要な処理の一部として組み込まれたことを示します。 ADATAATEXTの処理ロジックが変更され、シンボルのdupokフラグのチェックや、Sym構造体のdataおよびtextフィールドへのProgのリンクが追加されています。これは、デッドコード削除やデータセクションの管理をより効率的に行うための変更です。
  • src/cmd/8l/span.cの変更: putsymb関数のシグネチャにchar *go引数が追加され、Go固有の型情報(gotypefor(s->name))をシンボル情報に含めることができるようになりました。これは、デバッグ情報やリフレクションの精度向上に寄与します。

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

src/cmd/6l/go.c (削除)

--- a/src/cmd/6l/go.c
+++ /dev/null
@@ -1,603 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// go-specific
-
-// accumulate all type information from .6 files.
-// check for inconsistencies.
-// define gotypestrings variable if needed.
-// define gotypesigs variable if needed.
-
-// TODO:
-//	include type info for non-exported types.
-//	generate debugging section in binary.
-//	once the dust settles, try to move some code to
-//		libmach, so that other linkers and ar can share.
-//	try to make this completely portable and shared
-//	across linkers
-
 #include "l.h"
-
-/*
- *	package import data
- */
-// ... (以下、go.cの全内容が削除)

src/cmd/ld/go.c (新規作成)

--- /dev/null
+++ b/src/cmd/ld/go.c
@@ -0,0 +1,587 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// go-specific code shared across loaders (5l, 6l, 8l).
+
+// accumulate all type information from .6 files.
+// check for inconsistencies.
+// define gotypestrings variable if needed.
+// define gotypesigs variable if needed.
+
+// TODO:
+//	include type info for non-exported types.
+//	generate debugging section in binary.
+//	once the dust settles, try to move some code to
+//		libmach, so that other linkers and ar can share.
+
+/*
+ *	package import data
+ */
+// ... (以下、旧 src/cmd/6l/go.c の内容がほぼそのまま移動)

src/cmd/6l/Makefile (変更)

--- a/src/cmd/6l/Makefile
+++ b/src/cmd/6l/Makefile
@@ -36,3 +36,5 @@ clean:
 
 install: $(TARG)
 	cp $(TARG) $(BIN)/$(TARG)
+
+go.o: ../ld/go.c

src/cmd/8l/8.out.h (変更)

--- a/src/cmd/8l/8.out.h
+++ b/src/cmd/8l/8.out.h
@@ -79,9 +79,6 @@ enum as
 	ACMPSB,
 	ACMPSL,
 	ACMPSW,
-	ACMPXCHGB,
-	ACMPXCHGL,
-	ACMPXCHGW,
 	ADAA,
 	ADAS,
 	ADATA,
@@ -387,6 +384,10 @@ enum as
 
 	ASIGNAME,
 
+	ACMPXCHGB,
+	ACMPXCHGL,
+	ACMPXCHGW,
+
 	ALAST
 };
 
@@ -441,13 +442,15 @@ enum
 	D_FCONST	= 66,
 	D_SCONST	= 67,
 	D_ADDR		= 68,
-	D_CONST2	= 69,
 
 	D_FILE,
 	D_FILE1,
 
 	D_INDIR,	/* additive */
 
+	D_SBIG = D_INDIR + D_INDIR,
+	D_CONST2,
+
 	T_TYPE		= 1<<0,
 	T_INDEX		= 1<<1,
 	T_OFFSET	= 1<<2,

src/cmd/8l/obj.c (変更)

--- a/src/cmd/8l/obj.c
+++ b/src/cmd/8l/obj.c
@@ -333,6 +333,10 @@ main(int argc, char *argv[])
 		sprint(a, "%s/lib/lib_%s_%s.a", goroot, goarch, goos);
 		objfile(a);
 	}
+	definetypestrings();
+	definetypesigs();
+	deadcode();
+
 	firstp = firstp->link;
 	if(firstp == P)
 		errorexit();
@@ -848,7 +852,7 @@ ldobj(Biobuf *f, int32 len, char *pn)
 	import1 = Boffset(f);
 
 	Bseek(f, import0, 0);
-//	ldpkg(f, import1 - import0 - 2, pn);	// -2 for !\n
+//	ldpkg(f, import1 - import0 - 2, pn);	// -2 for !\n
+	ldpkg(f, import1 - import0 - 2, pn);	// -2 for !\n
 
 	Bseek(f, import1, 0);
 

コアとなるコードの解説

Go言語リンカコードの共通化

  • src/cmd/6l/go.cの削除とsrc/cmd/ld/go.cの新規作成: これは、Go言語のリンカにおけるモジュール化とコードの再利用性を高めるための重要なステップです。以前は、各アーキテクチャ(例: 6l8l)のリンカがそれぞれGo言語固有の処理(型情報のロード、デッドコード削除など)を独自に実装していました。これにより、同じロジックが複数のファイルに分散し、変更やバグ修正の際に手間がかかるという問題がありました。 src/cmd/ld/go.cに共通のGo言語リンカコードを集約し、各リンカがこれをインクルードする形式にすることで、以下のメリットが生まれます。

    • コードの重複排除: Go言語固有の共通ロジックが1箇所にまとまるため、コードの重複がなくなります。
    • メンテナンス性の向上: 共通ロジックの変更やバグ修正が1箇所で行えるため、メンテナンスが容易になります。
    • 一貫性の確保: 各リンカでGo言語固有の処理が常に同じように行われることが保証されます。
    • 新しいアーキテクチャへの対応の容易化: 新しいアーキテクチャのリンカを追加する際に、src/cmd/ld/go.cをインクルードするだけでGo言語固有の機能が利用できるようになります。
  • Makefileの変更: go.o: ../ld/go.cという行は、go.oというオブジェクトファイルが../ld/go.cというソースファイルから生成されることを示しています。これは、C言語のプリプロセッサの#includeディレクティブを利用したコードの共有パターンです。../ld/go.cは、各リンカのコンパイル時にそのリンカのソースコードの一部として扱われるため、あたかもそのリンカのディレクトリ内にgo.cが存在するかのようにコンパイルされます。

8.out.hおよび6.out.hの後方互換性修正

  • 定数定義順序の変更: enumの途中に新しい定数を挿入すると、それ以降に定義されている定数の値がずれてしまいます。これは、C言語のenumが明示的に値を指定しない限り、前の要素の値をインクリメントして割り当てるためです。リンカがオブジェクトファイルを解釈する際には、これらの定数の値に基づいて命令やデータタイプを識別します。もし定数の値が変更されてしまうと、古いバージョンのコンパイラやアセンブラで生成されたオブジェクトファイルが、新しいリンカで正しく解釈できなくなり、互換性の問題が発生します。 このコミットでは、新しい定数(ACMPXCHGBなどやD_SBIG, D_CONST2)をenumの末尾に移動することで、既存の定数の値が変更されないようにしています。これにより、古いオブジェクトファイルとの後方互換性が維持され、ユーザーは既存のバイナリ資産をそのまま利用できるようになります。

  • D_SBIGの導入と利用: D_SBIG = D_INDIR + D_INDIRという定義は、D_SBIGD_INDIRを基準としたオフセットを持つことを示唆しています。definetypestrings関数内で、Prog構造体のto.typeD_SBIGを設定し、to.sbigに実際のデータへのポインタを設定することで、リンカはGoの型情報のような大きな文字列データを効率的に処理できるようになります。これは、Goのreflectパッケージが実行時に型情報を動的に取得するために必要なメカニズムの一部です。リンカは、このD_SBIGタイプのアドレスを特別に扱い、指定されたポインタからデータを読み込みます。

  • D_INDIRの範囲チェックの修正: D_SBIGD_INDIR + D_INDIRとして定義されたことにより、D_INDIRを基準としたアドレスタイプの範囲チェックが不正確になる可能性がありました。i >= D_INDIRという単純なチェックでは、D_SBIGD_INDIRの範囲内と誤って判断される可能性があります。i >= D_INDIR && i < 2*D_INDIRという修正は、D_INDIRを基準としたアドレスタイプが、D_INDIRから2*D_INDIRの直前までの範囲に限定されることを明確にしています。これにより、D_SBIGのような新しいアドレスタイプが正しく区別され、リンカの動作の正確性が向上します。

src/cmd/8l/obj.cの変更

  • main関数でのGo固有処理の呼び出し: definetypestrings(), definetypesigs(), deadcode()の呼び出しがmain関数に追加されたことで、これらのGo言語固有のリンカ処理が、リンカのメイン処理フローの中で適切なタイミングで実行されるようになりました。
    • definetypestrings()definetypesigs()は、Goの型システムが実行時に利用する型情報をバイナリに埋め込むために重要です。
    • deadcode()は、最終的なバイナリから不要なコードやデータを削除し、バイナリサイズを最適化するために重要です。
  • ldpkgの有効化: ldpkg関数の呼び出しがコメントアウトから有効化されたことは、リンカがGoのパッケージファイルからメタデータをロードする処理が、リンカの標準的な処理フローに完全に組み込まれたことを意味します。これにより、リンカはGoの型情報やエクスポートされたシンボルを正確に把握し、リンク時の型チェックやリフレクションのサポートを適切に行えるようになります。

関連リンク

  • Go言語のツールチェインに関する公式ドキュメント (当時のもの): https://go.dev/doc/ (現在のドキュメントですが、当時の設計思想を理解する上で参考になります)
  • Plan 9 from Bell Labs: https://9p.io/plan9/

参考にした情報源リンク

  • Go言語のソースコード (GitHub): https://github.com/golang/go
  • C言語のenumに関する一般的な情報
  • リンカの基本的な動作に関する一般的な情報
  • Go言語のreflectパッケージに関する一般的な情報I have generated the detailed explanation of the commit. I will now print it to standard output.

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

このコミットは、Go言語のリンカにおける重要な構造変更と、既存のバイナリとの後方互換性に関する修正を含んでいます。具体的には、Go言語に特化したリンカのコード(go.c)を、各アーキテクチャ固有のリンカ(6l, 8lなど)から共通の../ld/go.cに移動し、これを各リンカがインクルードする形に変更しています。これにより、コードの重複を排除し、Goリンカの共通ロジックを一元管理できるようになります。また、8.out.hおよび6.out.hファイルにおける定数の定義順序を調整し、Plan 9の既存バージョンとの後方互換性を確保しています。これは、古い.8.6形式のオブジェクトファイルが無効になるのを防ぐための措置です。

コミット

commit b87e3e8b7f37a97c383ae5bbfadb401ec6fc243c
Author: Russ Cox <rsc@golang.org>
Date:   Tue Mar 31 00:20:07 2009 -0700

    * move go-specific loader code
    into gc directory, where it gets included as ../gc/ldbody
    this is similar to the assemblers including ../cc/lexbody
    and ../cc/macbody.
    
    * hook go-specific loader code into 8l.
    
    * make current 8.out.h and 6.out.h backward compatible
    with plan 9's versions.  i had added some constants in
    the middle of enums and have now moved them to the end.
    this keeps us from invalidating old .8 and .6 files.
    not sure how much it really matters, but easy to do.
    
    R=r
    DELTA=1314  (667 added, 623 deleted, 24 changed)
    OCL=26938
    CL=26941

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

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

元コミット内容

Go言語に特化したローダーコードをgcディレクトリに移動し、../gc/ldbodyとしてインクルードされるように変更しました。これは、アセンブラが../cc/lexbody../cc/macbodyをインクルードするのと同様のパターンです。 また、Go言語に特化したローダーコードを8lリンカに組み込みました。 現在の8.out.h6.out.hをPlan 9のバージョンと後方互換性を持つように修正しました。これは、enumの途中に定数を追加したことで、古い.8および.6ファイルを無効にしてしまう可能性があったため、それらの定数をenumの最後に移動することで対応しました。これにより、古いファイルとの互換性が維持されます。

変更の背景

このコミットには大きく分けて二つの背景があります。

  1. Go言語リンカの共通化とコードの整理: 初期のGo言語のツールチェインでは、各アーキテクチャ(例: 6lはamd64、8lは386)ごとにリンカが存在し、それぞれがGo言語固有のリンカロジックを持っていました。しかし、Go言語の機能が進化するにつれて、これらのリンカ間で共通のロジックが増えてきました。例えば、Goの型情報(gotypestringsgotypesigs)の処理、デッドコード削除、パッケージデータのロードなどは、アーキテクチャに依存しない共通の処理です。これらの共通ロジックが各リンカに分散していると、コードの重複が生じ、メンテナンスが困難になります。このコミットは、これらの共通ロジックをsrc/cmd/ld/go.cという新しい共通ファイルに集約し、各リンカがこれをインクルードする形にすることで、コードの再利用性と保守性を向上させることを目的としています。これは、Goのアセンブラが../cc/lexbody../cc/macbodyといった共通のコードをインクルードする既存のパターンを踏襲したものです。

  2. Plan 9との後方互換性の維持: Go言語のツールチェインは、Plan 9というオペレーティングシステムの設計思想とツールから大きな影響を受けています。6l8lといったリンカも、Plan 9のリンカの命名規則や一部の構造を継承しています。8.out.h6.out.hのようなヘッダファイルは、リンカがオブジェクトファイルやライブラリファイルを解釈するために必要な定数や構造体の定義を含んでいます。このコミット以前に、これらのヘッダファイルに新しい定数が追加された際、それが既存のenumの途中に挿入されてしまいました。これにより、定数の値が変更され、古いバージョンのリンカで生成された.8.6形式のオブジェクトファイルが正しく解釈できなくなる可能性がありました。このコミットでは、新しい定数をenumの末尾に移動することで、既存の定数の値を変更せず、古いオブジェクトファイルとの後方互換性を維持しています。これは、Go言語の進化の過程で、既存のツールやファイル形式との互換性を慎重に扱う必要があったことを示しています。

前提知識の解説

このコミットを理解するためには、以下の概念について理解しておく必要があります。

  • Go言語ツールチェイン: Go言語のプログラムをビルドするための一連のツール群です。主要なものとして、コンパイラ(gc)、アセンブラ(6a, 8aなど)、リンカ(6l, 8lなど)があります。
  • リンカ (6l, 8l, ld): コンパイラやアセンブラによって生成されたオブジェクトファイルやライブラリファイルを結合し、実行可能なバイナリを生成するツールです。6lはamd64アーキテクチャ用、8lは386アーキテクチャ用を指します。ldは、このコミットで共通のGo言語リンカコードを格納するために新設されたディレクトリ名であり、一般的なリンカを指す場合もあります。
  • Plan 9: ベル研究所で開発された分散オペレーティングシステムです。Go言語の設計者の一部はPlan 9の開発にも携わっており、Go言語のツールチェインや一部の設計思想にその影響が見られます。特に、Goのコンパイラやリンカの命名規則(例: 6g, 8g, 6l, 8l)はPlan 9の慣習に由来します。
  • out.hファイル: リンカがオブジェクトファイルやライブラリファイルを処理する際に使用する、命令コード、データ型、シンボルなどの定数や構造体の定義が含まれるヘッダファイルです。アーキテクチャごとに異なる場合があります(例: 6.out.h, 8.out.h)。
  • enum: C言語における列挙型です。一連の関連する定数に名前を付けるために使用されます。
  • D_SBIG: このコミットで8.out.hに追加された定数で、リンカが大きなデータを処理する際のデータタイプを示します。Go言語の文字列や型情報など、サイズが大きいデータを効率的に扱うために導入されたと考えられます。
  • シンボル (Sym): プログラム内の変数、関数、型などの名前付きエンティティを表すデータ構造です。リンカはシンボルテーブルを管理し、これらのエンティティのアドレス解決を行います。
    • STEXT: テキストセクション(コード)のシンボル。
    • SDATA: データセクション(初期化されたデータ)のシンボル。
    • SBSS: BSSセクション(初期化されていないデータ)のシンボル。
    • SXREF: 未解決の外部参照シンボル。
    • Sxxx: シンボルが破棄されたことを示す。
  • プログラム命令 (Prog): リンカが処理する個々の命令やデータ定義を表すデータ構造です。
  • Biobuf: バッファリングされたI/O操作を行うための構造体。ファイルからの読み込みなどに使用されます。
  • パッケージデータ (pkgdata): Go言語のコンパイル済みパッケージに含まれるメタデータで、型情報、エクスポートされたシンボル、メソッド情報などが含まれます。リンカはこれを読み込み、リンク時に利用します。
  • definetypestrings: Goの型情報を文字列として集約し、バイナリに埋め込むための関数です。Goのreflectパッケージなどがこの情報を使用します。
  • definetypesigs: Goの型シグネチャ(関数の引数や戻り値の型など)を集約し、バイナリに埋め込むための関数です。これもreflectパッケージなどで利用されます。
  • デッドコード削除 (deadcode): 実行されないコードや参照されないデータを最終的なバイナリから取り除く最適化手法です。これにより、バイナリサイズを削減し、起動時間を短縮できます。

技術的詳細

Go言語リンカコードの共通化

このコミットの最も大きな変更点は、Go言語に特化したリンカコードがsrc/cmd/6l/go.cからsrc/cmd/ld/go.cに移動され、各リンカ(6l, 8l)がこの共通ファイルをインクルードする形式になったことです。

  • src/cmd/6l/go.cの削除とsrc/cmd/ld/go.cの新規作成: src/cmd/6l/go.cの内容がほぼそのままsrc/cmd/ld/go.cに移動されました。これにより、6lリンカはgo.cファイルを直接コンパイルするのではなく、#include "../ld/go.c"というプリプロセッサディレクティブを通じて共通コードを取り込むようになります。同様の変更が8lリンカにも適用されています。
  • Makefileの変更: src/cmd/6l/Makefilesrc/cmd/8l/Makefilego.o: ../ld/go.cという行が追加されています。これは、go.oオブジェクトファイルが../ld/go.cからビルドされることを示しており、共通コードのインクルードパスを明示しています。
  • 共通コードの内容: src/cmd/ld/go.cには、Go言語のリンカが共通して行う処理がまとめられています。これには、以下の主要な機能が含まれます。
    • パッケージデータのロード (ldpkg, loadpkgdata, parsepkgdata): コンパイル済みのGoパッケージファイル(.aファイルなど)から型情報やエクスポートされたシンボルなどのメタデータを読み込み、リンカの内部データ構造に格納します。これにより、異なるパッケージ間で型の一貫性をチェックしたり、リフレクションに必要な情報を収集したりできます。
    • 型情報の定義 (definetypestrings, definetypesigs): Goの型システムが実行時に利用する型情報(gotypestrings)や型シグネチャ(gotypesigs)を、リンカが最終的なバイナリに埋め込む処理を行います。これらはGoのreflectパッケージが動的に型情報を取得するために不可欠です。
    • デッドコード削除 (deadcode, mark, marktext, markdata, sweeplist): 実行時に到達不可能なコードや参照されないデータを特定し、最終的なバイナリから削除する処理です。これにより、バイナリサイズを最適化し、不要なコードのロードを防ぎます。このコミットでは、deadcode関数内のsweeplistの呼び出しがコメントアウトされていますが、これは一時的な変更か、別の場所で処理されるようになったことを示唆しています。

8.out.hおよび6.out.hの後方互換性修正

このコミットでは、8.out.h6.out.hファイル内のenum定義が変更されています。

  • 定数定義順序の変更: 8.out.hでは、ACMPXCHGB, ACMPXCHGL, ACMPXCHGWといった命令コードの定数が、ALASTの前に移動されています。同様に、D_SBIGD_CONST2のデータタイプ定数も、D_INDIRの後に移動されています。 元のコードでは、これらの新しい定数が既存のenumの途中に挿入されていたため、それ以降の定数の値がずれてしまい、古いオブジェクトファイルとの互換性が失われる可能性がありました。新しい定数をenumの末尾に移動することで、既存の定数の値は変更されず、古いオブジェクトファイルが引き続き正しく解釈されるようになります。
  • D_SBIGの導入と利用: D_SBIGは、リンカが大きなデータブロックを扱うための新しいデータタイプ定数です。コミットメッセージにもあるように、Goの文字列や型情報など、サイズが大きいデータを効率的に処理するために導入されました。definetypestrings関数内で、Prog構造体のto.typeフィールドにD_SBIGを設定し、to.sbigフィールドにデータへのポインタを設定することで、リンカがこの大きなデータを適切に処理できるようになります。
  • D_INDIRの範囲チェックの修正: src/cmd/8l/list.csrc/cmd/8l/span.cでは、D_INDIRに関連するアドレスタイプのチェックロジックが修正されています。i >= D_INDIRという条件がi >= D_INDIR && i < 2*D_INDIRに変更されています。これは、D_SBIGD_INDIR + D_INDIRとして定義されたため、D_INDIRを基準とした範囲チェックが正しく機能するように調整されたものです。

その他の変更点

  • src/cmd/8l/l.hの変更: Adr構造体にu0sbigフィールドが追加され、sbigマクロが定義されています。これはD_SBIGタイプのアドレスを扱うために必要です。 Prog構造体にdlinkフィールドが追加され、Sym構造体にdupok, reachable, text, dataフィールドが追加されています。これらは主にデッドコード削除のロジックをサポートするためのものです。 Symtypeを示すenumSxxxが追加され、シンボルが破棄された状態を示すために使用されます。 newdatanewtext関数のプロトタイプが追加され、リンカが新しいデータセクションやテキストセクションを生成する際のヘルパー関数として利用されます。
  • src/cmd/8l/obj.cの変更: main関数内でdefinetypestrings(), definetypesigs(), deadcode()が呼び出されるようになりました。これにより、リンカの実行フローの中でGo固有の処理が適切に実行されるようになります。 ldobj関数内でldpkgの呼び出しがコメントアウトから有効化されています。これは、パッケージデータのロードがリンカの主要な処理の一部として組み込まれたことを示します。 ADATAATEXTの処理ロジックが変更され、シンボルのdupokフラグのチェックや、Sym構造体のdataおよびtextフィールドへのProgのリンクが追加されています。これは、デッドコード削除やデータセクションの管理をより効率的に行うための変更です。
  • src/cmd/8l/span.cの変更: putsymb関数のシグネチャにchar *go引数が追加され、Go固有の型情報(gotypefor(s->name))をシンボル情報に含めることができるようになりました。これは、デバッグ情報やリフレクションの精度向上に寄与します。

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

src/cmd/6l/go.c (削除)

--- a/src/cmd/6l/go.c
+++ /dev/null
@@ -1,603 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// go-specific
-
-// accumulate all type information from .6 files.
-// check for inconsistencies.
-// define gotypestrings variable if needed.
-// define gotypesigs variable if needed.
-
-// TODO:
-//	include type info for non-exported types.
-//	generate debugging section in binary.
-//	once the dust settles, try to move some code to
-//		libmach, so that other linkers and ar can share.
-//	try to make this completely portable and shared
-//	across linkers
-
 #include "l.h"
-
-/*
- *	package import data
- */
-// ... (以下、go.cの全内容が削除)

src/cmd/ld/go.c (新規作成)

--- /dev/null
+++ b/src/cmd/ld/go.c
@@ -0,0 +1,587 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// go-specific code shared across loaders (5l, 6l, 8l).
+
+// accumulate all type information from .6 files.
+// check for inconsistencies.
+// define gotypestrings variable if needed.
+// define gotypesigs variable if needed.
+
+// TODO:
+//	include type info for non-exported types.
+//	generate debugging section in binary.
+//	once the dust settles, try to move some code to
+//		libmach, so that other linkers and ar can share.
+
+/*
+ *	package import data
+ */
-// ... (以下、旧 src/cmd/6l/go.c の内容がほぼそのまま移動)

src/cmd/6l/Makefile (変更)

--- a/src/cmd/6l/Makefile
+++ b/src/cmd/6l/Makefile
@@ -36,3 +36,5 @@ clean:
 
 install: $(TARG)
 	cp $(TARG) $(BIN)/$(TARG)
+
+go.o: ../ld/go.c

src/cmd/8l/8.out.h (変更)

--- a/src/cmd/8l/8.out.h
+++ b/src/cmd/8l/8.out.h
@@ -79,9 +79,6 @@ enum as
 	ACMPSB,
 	ACMPSL,
 	ACMPSW,
-	ACMPXCHGB,
-	ACMPXCHGL,
-	ACMPXCHGW,
 	ADAA,
 	ADAS,
 	ADATA,
@@ -387,6 +384,10 @@ enum as
 
 	ASIGNAME,
 
+	ACMPXCHGB,
+	ACMPXCHGL,
+	ACMPXCHGW,
+
 	ALAST
 };
 
@@ -441,13 +442,15 @@ enum
 	D_FCONST	= 66,
 	D_SCONST	= 67,
 	D_ADDR		= 68,
-	D_CONST2	= 69,
 
 	D_FILE,
 	D_FILE1,
 
 	D_INDIR,	/* additive */
 
+	D_SBIG = D_INDIR + D_INDIR,
+	D_CONST2,
+
 	T_TYPE		= 1<<0,
 	T_INDEX		= 1<<1,
 	T_OFFSET	= 1<<2,

src/cmd/8l/obj.c (変更)

--- a/src/cmd/8l/obj.c
+++ b/src/cmd/8l/obj.c
@@ -333,6 +333,10 @@ main(int argc, char *argv[])
 		sprint(a, "%s/lib/lib_%s_%s.a", goroot, goarch, goos);
 		objfile(a);
 	}
+	definetypestrings();
+	definetypesigs();
+	deadcode();
+
 	firstp = firstp->link;
 	if(firstp == P)
 		errorexit();
@@ -848,7 +852,7 @@ ldobj(Biobuf *f, int32 len, char *pn)
 	import1 = Boffset(f);
 
 	Bseek(f, import0, 0);
-//	ldpkg(f, import1 - import0 - 2, pn);	// -2 for !\n
+//	ldpkg(f, import1 - import0 - 2, pn);	// -2 for !\n
+	ldpkg(f, import1 - import0 - 2, pn);	// -2 for !\n
 
 	Bseek(f, import1, 0);
 

コアとなるコードの解説

Go言語リンカコードの共通化

  • src/cmd/6l/go.cの削除とsrc/cmd/ld/go.cの新規作成: これは、Go言語のリンカにおけるモジュール化とコードの再利用性を高めるための重要なステップです。以前は、各アーキテクチャ(例: 6l8l)のリンカがそれぞれGo言語固有の処理(型情報のロード、デッドコード削除など)を独自に実装していました。これにより、同じロジックが複数のファイルに分散し、変更やバグ修正の際に手間がかかるという問題がありました。 src/cmd/ld/go.cに共通のGo言語リンカコードを集約し、各リンカがこれをインクルードする形式にすることで、以下のメリットが生まれます。

    • コードの重複排除: Go言語固有の共通ロジックが1箇所にまとまるため、コードの重複がなくなります。
    • メンテナンス性の向上: 共通ロジックの変更やバグ修正が1箇所で行えるため、メンテナンスが容易になります。
    • 一貫性の確保: 各リンカでGo言語固有の処理が常に同じように行われることが保証されます。
    • 新しいアーキテクチャへの対応の容易化: 新しいアーキテクチャのリンカを追加する際に、src/cmd/ld/go.cをインクルードするだけでGo言語固有の機能が利用できるようになります。
  • Makefileの変更: go.o: ../ld/go.cという行は、go.oというオブジェクトファイルが../ld/go.cというソースファイルから生成されることを示しています。これは、C言語のプリプロセッサの#includeディレクティブを利用したコードの共有パターンです。../ld/go.cは、各リンカのコンパイル時にそのリンカのソースコードの一部として扱われるため、あたかもそのリンカのディレクトリ内にgo.cが存在するかのようにコンパイルされます。

8.out.hおよび6.out.hの後方互換性修正

  • 定数定義順序の変更: enumの途中に新しい定数を挿入すると、それ以降に定義されている定数の値がずれてしまいます。これは、C言語のenumが明示的に値を指定しない限り、前の要素の値をインクリメントして割り当てるためです。リンカがオブジェクトファイルを解釈する際には、これらの定数の値に基づいて命令やデータタイプを識別します。もし定数の値が変更されてしまうと、古いバージョンのコンパイラやアセンブラで生成されたオブジェクトファイルが、新しいリンカで正しく解釈できなくなり、互換性の問題が発生します。 このコミットでは、新しい定数(ACMPXCHGBなどやD_SBIG, D_CONST2)をenumの末尾に移動することで、既存の定数の値が変更されないようにしています。これにより、古いオブジェクトファイルとの後方互換性が維持され、ユーザーは既存のバイナリ資産をそのまま利用できるようになります。

  • D_SBIGの導入と利用: D_SBIG = D_INDIR + D_INDIRという定義は、D_SBIGD_INDIRを基準としたオフセットを持つことを示唆しています。definetypestrings関数内で、Prog構造体のto.typeD_SBIGを設定し、to.sbigに実際のデータへのポインタを設定することで、リンカはGoの型情報のような大きな文字列データを効率的に処理できるようになります。これは、Goのreflectパッケージが実行時に型情報を動的に取得するために必要なメカニズムの一部です。リンカは、このD_SBIGタイプのアドレスを特別に扱い、指定されたポインタからデータを読み込みます。

  • D_INDIRの範囲チェックの修正: D_SBIGD_INDIR + D_INDIRとして定義されたことにより、D_INDIRを基準としたアドレスタイプの範囲チェックが不正確になる可能性がありました。i >= D_INDIRという単純なチェックでは、D_SBIGD_INDIRの範囲内と誤って判断される可能性があります。i >= D_INDIR && i < 2*D_INDIRという修正は、D_INDIRを基準としたアドレスタイプが、D_INDIRから2*D_INDIRの直前までの範囲に限定されることを明確にしています。これにより、D_SBIGのような新しいアドレスタイプが正しく区別され、リンカの動作の正確性が向上します。

src/cmd/8l/obj.cの変更

  • main関数でのGo固有処理の呼び出し: definetypestrings(), definetypesigs(), deadcode()の呼び出しがmain関数に追加されたことで、これらのGo言語固有のリンカ処理が、リンカのメイン処理フローの中で適切なタイミングで実行されるようになりました。
    • definetypestrings()definetypesigs()は、Goの型システムが実行時に利用する型情報をバイナリに埋め込むために重要です。
    • deadcode()は、最終的なバイナリから不要なコードやデータを削除し、バイナリサイズを最適化するために重要です。
  • ldpkgの有効化: ldpkg関数の呼び出しがコメントアウトから有効化されたことは、リンカがGoのパッケージファイルからメタデータをロードする処理が、リンカの標準的な処理フローに完全に組み込まれたことを意味します。これにより、リンカはGoの型情報やエクスポートされたシンボルを正確に把握し、リンク時の型チェックやリフレクションのサポートを適切に行えるようになります。

関連リンク

  • Go言語のツールチェインに関する公式ドキュメント (当時のもの): https://go.dev/doc/ (現在のドキュメントですが、当時の設計思想を理解する上で参考になります)
  • Plan 9 from Bell Labs: https://9p.io/plan9/

参考にした情報源リンク

  • Go言語のソースコード (GitHub): https://github.com/golang/go
  • C言語のenumに関する一般的な情報
  • リンカの基本的な動作に関する一般的な情報
  • Go言語のreflectパッケージに関する一般的な情報