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

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

このコミットは、Go言語のリンカ (cmd/5l, cmd/6l, cmd/8l) において、CALL reg, reg という形式の命令を受け入れるように変更するものです。これは、呼び出し元の関数が呼び出される関数に渡すレジスタの値をリンカが認識し、最適化のヒントとして利用できるようにするための変更です。

コミット

commit d57fcbf05c431fa767467b2a04f477309b681f9d
Author: Russ Cox <rsc@golang.org>
Date:   Fri Feb 22 14:23:21 2013 -0500

    cmd/5l, cmd/6l, cmd/8l: accept CALL reg, reg
    
    The new src argument is ignored during linking
    (that is, CALL r1, r2 is identical to CALL r2 for linking),
    but it serves as a hint to the 5g/6g/8g optimizer
    that the src register is live on entry to the called
    function and must be preserved.
    
    It is possible to avoid exposing this fact to the rest of
    the toolchain, keeping it entirely within 5g/6g/8g,
    but I think it will help to be able to look in object files
    and assembly listings and linker -a / -W output to
    see CALL instructions are "Go func value" calls and
    which are "C function pointer" calls.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/7364045

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

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

元コミット内容

cmd/5l, cmd/6l, cmd/8l リンカが CALL reg, reg 形式の命令を受け入れるように変更されました。

この新しい src 引数(CALL r1, r2r1)は、リンカの処理中には無視されます(つまり、リンカにとっては CALL r1, r2CALL r2 と同じです)。しかし、これは 5g/6g/8g オプティマイザに対して、呼び出される関数へのエントリ時に src レジスタが「ライブ」(有効な値を持っている)であり、その値が保持されなければならないというヒントとして機能します。

この事実をツールチェーンの他の部分に公開せず、5g/6g/8g の内部に完全に留めることも可能でした。しかし、オブジェクトファイル、アセンブリリスト、リンカの -a / -W 出力を見て、どの CALL 命令が「Goの関数値呼び出し」であり、どの CALL 命令が「Cの関数ポインタ呼び出し」であるかを区別できるようにすることは有用であると考えられました。

変更の背景

Go言語のコンパイラとリンカのツールチェーンにおいて、関数呼び出しの最適化とデバッグ情報の改善が背景にあります。特に、関数ポインタを介した間接的な関数呼び出し(Goの関数値呼び出しやCの関数ポインタ呼び出し)において、呼び出し規約やレジスタの使用状況に関するより詳細な情報をツールチェーン全体で共有する必要がありました。

従来の CALL 命令は通常、呼び出し先のアドレスのみを指定していましたが、この変更により、呼び出し元が特定のレジスタの値を呼び出し先で保持してほしいという意図をリンカに伝えることができるようになります。これは、コンパイラ(5g/6g/8g)が生成するコードの最適化において重要なヒントとなり、特にガベージコレクションやスタック管理において、どのレジスタが有効なポインタを含んでいるかを正確に追跡するために役立ちます。

また、この情報はリンカの出力(オブジェクトファイルやアセンブリリスト)にも反映されるため、開発者やデバッガが生成されたコードを分析する際に、特定の CALL 命令がGoのランタイムにおける関数値呼び出しなのか、それとも外部のC関数への呼び出しなのかを視覚的に区別できるようになり、デバッグやプロファイリングの効率が向上します。

前提知識の解説

Go言語のツールチェーンとクロスコンパイル

Go言語は、その強力なクロスコンパイル機能で知られています。これは、異なるアーキテクチャやオペレーティングシステム向けに実行ファイルを生成できることを意味します。このコミットで言及されている cmd/5l, cmd/6l, cmd/8l は、Goのリンカの一部であり、それぞれ特定のCPUアーキテクチャに対応しています。

  • cmd/5l: ARMアーキテクチャ (ARMv5, ARMv6, ARMv7) 向けのリンカです。Goの初期には GOARCH=arm に対応していました。
  • cmd/6l: AMD64 (x86-64) アーキテクチャ向けのリンカです。現代のほとんどのデスクトップやサーバー環境で使われています。
  • cmd/8l: 386 (x86) アーキテクチャ向けのリンカです。32ビットシステムで使われます。

これらのリンカは、コンパイラ (5g, 6g, 8g など) が生成したオブジェクトファイルを結合し、最終的な実行可能ファイルを生成する役割を担います。

CALL 命令とレジスタ

アセンブリ言語における CALL 命令は、サブルーチンや関数を呼び出すために使用されます。通常、CALL 命令は呼び出し先のメモリアドレスをオペランドとして取ります。関数が呼び出されると、現在の命令ポインタ(PC レジスタ)の値がスタックに保存され、実行フローが呼び出し先にジャンプします。関数が終了すると、スタックから保存された PC の値が復元され、呼び出し元に戻ります。

CPUのレジスタは、CPU内部にある高速な記憶領域であり、演算やデータ転送の際に一時的にデータを保持するために使われます。関数呼び出しにおいては、引数の受け渡しや戻り値の格納、一時的な計算結果の保持などにレジスタが頻繁に利用されます。

オプティマイザ (5g/6g/8g)

5g, 6g, 8g は、それぞれARM, AMD64, 386アーキテクチャに対応するGo言語のコンパイラです。これらのコンパイラには、生成される機械語コードの性能を向上させるためのオプティマイザが組み込まれています。オプティマイザは、コードの実行速度を速くしたり、メモリ使用量を減らしたりするために、様々な変換(例えば、不要な命令の削除、レジスタ割り当ての最適化、ループの展開など)を行います。

このコミットの文脈では、オプティマイザが関数呼び出しの際に、どのレジスタが呼び出し後も有効な値を保持している必要があるか(「ライブである」)という情報を利用して、より効率的なコードを生成することが意図されています。

オブジェクトファイルとアセンブリリスト

  • オブジェクトファイル: コンパイラがソースコードをコンパイルした結果生成される中間ファイルです。まだリンカによって結合されていない、機械語コードとシンボル情報(関数名、変数名など)が含まれています。
  • アセンブリリスト: ソースコードがどのようにアセンブリ言語に変換されたかを示すリストです。デバッグやパフォーマンス分析のために、開発者が機械語レベルの動作を理解するのに役立ちます。

リンカの -a-W オプションは、リンカの動作に関する詳細な情報や警告を出力するために使用されることがあります。

技術的詳細

この変更の核心は、リンカが CALL 命令の新しい形式 CALL reg, reg を認識し、処理できるようにすることです。従来の CALL 命令は通常、単一のオペランド(呼び出し先のアドレス)を取りますが、この拡張により、2つのレジスタをオペランドとして取ることが可能になります。

コミットメッセージによると、CALL r1, r2 の形式において、r1 は「src (source) 引数」と呼ばれ、r2 は「dest (destination) 引数」または単に呼び出し先のアドレスを指すレジスタとして機能します。

重要な点は以下の通りです。

  1. リンカの動作: リンカ (5l, 6l, 8l) は、この新しい src 引数 (r1) を無視します。つまり、リンカの視点からは CALL r1, r2CALL r2 と全く同じように扱われます。リンカは、r2 が指すアドレスへのジャンプ命令を生成するだけです。
  2. オプティマイザへのヒント: src 引数 (r1) の真の目的は、コンパイラ (5g, 6g, 8g) のオプティマイザへのヒントとして機能することです。このヒントは、「呼び出される関数にエントリする時点で、r1 レジスタがライブであり、その値が呼び出し後も保持される必要がある」ということを示します。これは、特にGoのガベージコレクタがポインタを正確に追跡するために、どのレジスタが有効なポインタを含んでいるかを把握する必要がある場合に重要です。
  3. デバッグと可視性: この変更は、オブジェクトファイルやアセンブリリスト、リンカの出力に CALL reg, reg の形式が明示的に現れることを可能にします。これにより、開発者は、特定の CALL 命令がGoのランタイムが生成する「関数値呼び出し」(Goの func 型の変数を介した呼び出し)なのか、それともC言語のライブラリ関数などへの「C関数ポインタ呼び出し」なのかを区別できるようになります。この区別は、デバッグやパフォーマンス分析において非常に有用です。例えば、Goの関数値呼び出しは、通常、呼び出し規約やスタックフレームの管理において、C関数ポインタ呼び出しとは異なる特性を持つため、この情報が可視化されることで、より深いレベルでの理解と問題解決が可能になります。

optab.cycall

Goのリンカのソースコードにおいて、optab.c ファイルは、各命令(オペコード)の処理方法を定義するテーブルを含んでいます。このテーブルは、リンカがアセンブリ命令を解析し、対応する機械語コードを生成するために使用されます。

ycall は、CALL 命令に関連するオペランドの型や処理方法を定義する配列(または構造体)であると推測されます。この配列は、リンカが CALL 命令に続くオペランドの組み合わせをどのように解釈し、検証するかを決定します。

変更点を見ると、ycall 配列に新しいエントリが追加されています。

  • Ynone, Yml, Zo_m64, 2, の行が Ynone, Yml, Zo_m64, 0, に変更され、さらに Yrx, Yrx, Zo_m64, 2, が追加されています。
    • Ynone: オペランドなし
    • Yml: メモリまたはレジスタのオペランド
    • Yrx: レジスタのオペランド
    • Zo_m64: 64ビットのメモリオペランド
    • Zo_m: 32ビットのメモリオペランド
    • 最後の数値 (20) は、命令のバイト長やその他のフラグに関連している可能性があります。

この変更は、リンカが CALL 命令に対して、Yrx, Yrx の組み合わせ、つまり2つのレジスタオペランドを持つ形式を新たに認識し、処理できるようにするためのものです。

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

diff --git a/src/cmd/5l/optab.c b/src/cmd/5l/optab.c
index 1e93a3113b..231071f209 100644
--- a/src/cmd/5l/optab.c
+++ b/src/cmd/5l/optab.c
@@ -63,6 +63,7 @@ Optab	optab[] =
 
 	{ AB,		C_NONE,	C_NONE,	C_ROREG,	 6, 4, 0,	LPOOL },
 	{ ABL,		C_NONE,	C_NONE,	C_ROREG,	 7, 8, 0 },
+	{ ABL,		C_REG,	C_NONE,	C_ROREG,	 7, 8, 0 },
 	{ ABX,		C_NONE,	C_NONE,	C_ROREG,	 75, 12, 0 },
 	{ ABXRET,	C_NONE,	C_NONE,	C_ROREG,	 76, 4, 0 },
 
diff --git a/src/cmd/6l/optab.c b/src/cmd/6l/optab.c
index 43f34d9747..21b4784353 100644
--- a/src/cmd/6l/optab.c
+++ b/src/cmd/6l/optab.c
@@ -326,7 +326,8 @@ uchar	yloop[] =
 };
 uchar	ycall[] =
 {
-\tYnone,\tYml,\tZo_m64,\t2,\n+\tYnone,\tYml,\tZo_m64,\t0,\n+\tYrx,\tYrx,\tZo_m64,\t2,\n \tYnone,\tYbr,\tZcall,\t1,\n \t0
 };
diff --git a/src/cmd/8l/optab.c b/src/cmd/8l/optab.c
index ae808ec770..3ccdbfd226 100644
--- a/src/cmd/8l/optab.c
+++ b/src/cmd/8l/optab.c
@@ -266,7 +266,8 @@ uchar	yloop[] =
 };
 uchar	ycall[] =
 {
-\tYnone,\tYml,\tZo_m,\t2,\n+\tYnone,\tYml,\tZo_m,\t0,\n+\tYrx,\tYrx,\tZo_m,\t2,\n \tYnone,\tYcol,\tZcallind,\t2,\n \tYnone,\tYbr,\tZcall,\t0,\n \tYnone,\tYi32,\tZcallcon,\t1,\n```

## コアとなるコードの解説

### `src/cmd/5l/optab.c` の変更

ARMアーキテクチャ向けのリンカ (`5l`) の `optab.c` では、`ABL` (Branch with Link) 命令の定義に新しいエントリが追加されています。

```c
	{ ABL,		C_NONE,	C_NONE,	C_ROREG,	 7, 8, 0 },
+	{ ABL,		C_REG,	C_NONE,	C_ROREG,	 7, 8, 0 },
  • ABL: ARMの Branch with Link 命令。これは関数呼び出しに使用され、呼び出し元のアドレスをリンクレジスタ (LR) に保存してからジャンプします。
  • C_NONE: オペランドなし。
  • C_REG: レジスタオペランド。
  • C_ROREG: 読み出し専用レジスタオペランド。

追加された行は、ABL 命令が C_REG (ソースレジスタ) と C_ROREG (デスティネーションレジスタ) の2つのレジスタオペランドを取る形式をサポートすることを示しています。これにより、ABL Rsrc, Rdest のような形式の命令がリンカによって正しく解析されるようになります。

src/cmd/6l/optab.c の変更

AMD64アーキテクチャ向けのリンカ (6l) の optab.c では、ycall 配列が変更されています。

uchar	ycall[] =
{
-\tYnone,\tYml,\tZo_m64,\t2,\n+\tYnone,\tYml,\tZo_m64,\t0,\n+\tYrx,\tYrx,\tZo_m64,\t2,\n \tYnone,\tYbr,\tZcall,\t1,\n \t0
};
  • Ynone: オペランドなし。
  • Yml: メモリまたはレジスタのオペランド。
  • Yrx: レジスタのオペランド。
  • Zo_m64: 64ビットのメモリオペランド。

変更前は Ynone, Yml, Zo_m64, 2, というエントリがありましたが、これが Ynone, Yml, Zo_m64, 0, に変更され、さらに Yrx, Yrx, Zo_m64, 2, という新しいエントリが追加されています。 これは、CALL 命令が以下の2つの形式をサポートすることを示しています。

  1. Ynone, Yml, Zo_m64: 従来の CALL 命令で、単一のメモリまたはレジスタオペランド(呼び出し先アドレス)を取る形式。最後の数値が 2 から 0 に変更されていますが、これは命令のバイト長やリンカ内部のフラグに関連する可能性があります。
  2. Yrx, Yrx, Zo_m64: 新しく追加された形式で、2つのレジスタオペランド (Yrx, Yrx) を取る CALL 命令をサポートします。これは CALL Rsrc, Rdest の形式に対応し、Zo_m64 は64ビットのオペランドサイズを示唆しています。

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

386アーキテクチャ向けのリンカ (8l) の optab.c でも、ycall 配列が同様に変更されています。

uchar	ycall[] =
{
-\tYnone,\tYml,\tZo_m,\t2,\n+\tYnone,\tYml,\tZo_m,\t0,\n+\tYrx,\tYrx,\tZo_m,\t2,\n \tYnone,\tYcol,\tZcallind,\t2,\n \tYnone,\tYbr,\tZcall,\t0,\n \tYnone,\tYi32,\tZcallcon,\t1,\n```

*   `Zo_m`: 32ビットのメモリオペランド。

`6l` と同様に、`Ynone, Yml, Zo_m, 2,` が `Ynone, Yml, Zo_m, 0,` に変更され、`Yrx, Yrx, Zo_m, 2,` が追加されています。これは、386アーキテクチャにおいても、`CALL` 命令が2つのレジスタオペランドを取る形式をサポートするように拡張されたことを意味します。`Zo_m` は32ビットのオペランドサイズに対応しています。

これらの変更により、Goのリンカは、コンパイラが生成する `CALL reg, reg` 形式の命令を正しく解釈し、最終的な実行可能ファイルに含めることができるようになります。これにより、コンパイラはより詳細なレジスタのライブネス情報をリンカに伝え、デバッグ情報の可視性を向上させることが可能になります。

## 関連リンク

*   Go言語のコンパイラとリンカに関する公式ドキュメント (当時のもの): [https://golang.org/doc/](https://golang.org/doc/) (現在のドキュメントは変更されている可能性があります)
*   Go言語のツールチェーンの歴史と進化に関する議論: Goのメーリングリストやデザインドキュメントなどを参照。

## 参考にした情報源リンク

*   Go言語の公式ドキュメント (現在のもの): [https://go.dev/doc/](https://go.dev/doc/)
*   Go言語のソースコード (GitHub): [https://github.com/golang/go](https://github.com/golang/go)
*   アセンブリ言語の `CALL` 命令に関する一般的な情報 (例: Wikipedia, CPUアーキテクチャのドキュメント)
*   コンパイラ最適化に関する一般的な情報 (例: コンパイラ設計の教科書)
*   Go言語のリンカの内部構造に関する情報 (Goのソースコードリーディングや関連するブログ記事)
*   コミットに記載されている `https://golang.org/cl/7364045` は、Goのコードレビューシステム (Gerrit) のチェンジリストへのリンクです。これは、このコミットがマージされるまでの議論やレビューの履歴を確認するために非常に有用です。
# [インデックス 15378] ファイルの概要

このコミットは、Go言語のリンカ (`cmd/5l`, `cmd/6l`, `cmd/8l`) において、`CALL reg, reg` という形式の命令を受け入れるように変更するものです。これは、呼び出し元の関数が呼び出される関数に渡すレジスタの値をリンカが認識し、最適化のヒントとして利用できるようにするための変更です。

## コミット

commit d57fcbf05c431fa767467b2a04f477309b681f9d Author: Russ Cox rsc@golang.org Date: Fri Feb 22 14:23:21 2013 -0500

cmd/5l, cmd/6l, cmd/8l: accept CALL reg, reg

The new src argument is ignored during linking
(that is, CALL r1, r2 is identical to CALL r2 for linking),
but it serves as a hint to the 5g/6g/8g optimizer
that the src register is live on entry to the called
function and must be preserved.

It is possible to avoid exposing this fact to the rest of
the toolchain, keeping it entirely within 5g/6g/8g,
but I think it will help to be able to look in object files
and assembly listings and linker -a / -W output to
see CALL instructions are "Go func value" calls and
which are "C function pointer" calls.

R=ken2
CC=golang-dev
https://golang.org/cl/7364045

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

[https://github.com/golang/go/commit/d57fcbf05c431fa767467b2a04f477309b681f9d](https://github.com/golang/go/commit/d57fcbf05c431fa767467b2a04f477309b681f9d)

## 元コミット内容

`cmd/5l`, `cmd/6l`, `cmd/8l` リンカが `CALL reg, reg` 形式の命令を受け入れるように変更されました。

この新しい `src` 引数(`CALL r1, r2` の `r1`)は、リンカの処理中には無視されます(つまり、リンカにとっては `CALL r1, r2` は `CALL r2` と同じです)。しかし、これは `5g`/`6g`/`8g` オプティマイザに対して、呼び出される関数へのエントリ時に `src` レジスタが「ライブ」(有効な値を持っている)であり、その値が保持されなければならないというヒントとして機能します。

この事実をツールチェーンの他の部分に公開せず、`5g`/`6g`/`8g` の内部に完全に留めることも可能でした。しかし、オブジェクトファイル、アセンブリリスト、リンカの `-a` / `-W` 出力を見て、どの `CALL` 命令が「Goの関数値呼び出し」であり、どの `CALL` 命令が「Cの関数ポインタ呼び出し」であるかを区別できるようにすることは有用であると考えられました。

## 変更の背景

Go言語のコンパイラとリンカのツールチェーンにおいて、関数呼び出しの最適化とデバッグ情報の改善が背景にあります。特に、関数ポインタを介した間接的な関数呼び出し(Goの関数値呼び出しやCの関数ポインタ呼び出し)において、呼び出し規約やレジスタの使用状況に関するより詳細な情報をツールチェーン全体で共有する必要がありました。

従来の `CALL` 命令は通常、呼び出し先のアドレスのみを指定していましたが、この変更により、呼び出し元が特定のレジスタの値を呼び出し先で保持してほしいという意図をリンカに伝えることができるようになります。これは、コンパイラ(`5g`/`6g`/`8g`)が生成するコードの最適化において重要なヒントとなり、特にガベージコレクションやスタック管理において、どのレジスタが有効なポインタを含んでいるかを正確に追跡するために役立ちます。

また、この情報はリンカの出力(オブジェクトファイルやアセンブリリスト)にも反映されるため、開発者やデバッガが生成されたコードを分析する際に、特定の `CALL` 命令がGoのランタイムにおける関数値呼び出しなのか、それとも外部のC関数への呼び出しなのかを視覚的に区別できるようになり、デバッグやプロファイリングの効率が向上します。

## 前提知識の解説

### Go言語のツールチェーンとクロスコンパイル

Go言語は、その強力なクロスコンパイル機能で知られています。これは、異なるアーキテクチャやオペレーティングシステム向けに実行ファイルを生成できることを意味します。このコミットで言及されている `cmd/5l`, `cmd/6l`, `cmd/8l` は、Goのリンカの一部であり、それぞれ特定のCPUアーキテクチャに対応していました。

*   **`cmd/5l`**: ARMアーキテクチャ (ARMv5, ARMv6, ARMv7) 向けのリンカです。Goの初期には `GOARCH=arm` に対応していました。
*   **`cmd/6l`**: AMD64 (x86-64) アーキテクチャ向けのリンカです。現代のほとんどのデスクトップやサーバー環境で使われています。
*   **`8l`**: 386 (x86) アーキテクチャ向けのリンカです。32ビットシステムで使われます。

これらのリンカは、コンパイラ (`5g`, `6g`, `8g` など) が生成したオブジェクトファイルを結合し、最終的な実行可能ファイルを生成する役割を担います。

**補足**: 現代のGoツールチェーンでは、これらの個別のリンカコマンドは統合され、`go tool link` コマンドによって処理されます。`go build` が自動的に `go tool link` を呼び出します。Goプロジェクトは、Go自身で書かれた内部リンカの開発に大きく投資しており、パフォーマンスと保守性の向上を図っています。内部リンカがデフォルトですが、CGO(GoコードがCコードと連携する場合)が関与する場合は、`clang` や `gcc` のような外部リンカも利用できます。リンカの役割は、コンパイルされたGoパッケージとその依存関係を受け取り、シンボルを解決し、未使用のコードを削除し、デバッグ情報(DWARFなど)を生成し、再配置を実行して最終的な実行可能バイナリを生成することです。

### `CALL` 命令とレジスタ

アセンブリ言語における `CALL` 命令は、サブルーチンや関数を呼び出すために使用されます。通常、`CALL` 命令は呼び出し先のメモリアドレスをオペランドとして取ります。関数が呼び出されると、現在の命令ポインタ(`PC` レジスタ)の値がスタックに保存され、実行フローが呼び出し先にジャンプします。関数が終了すると、スタックから保存された `PC` の値が復元され、呼び出し元に戻ります。

CPUの**レジスタ**は、CPU内部にある高速な記憶領域であり、演算やデータ転送の際に一時的にデータを保持するために使われます。関数呼び出しにおいては、引数の受け渡しや戻り値の格納、一時的な計算結果の保持などにレジスタが頻繁に利用されます。

### オプティマイザ (`5g`/`6g`/`8g`)

`5g`, `6g`, `8g` は、それぞれARM, AMD64, 386アーキテクチャに対応するGo言語のコンパイラです。これらのコンパイラには、生成される機械語コードの性能を向上させるための**オプティマイザ**が組み込まれています。オプティマイザは、コードの実行速度を速くしたり、メモリ使用量を減らしたりするために、様々な変換(例えば、不要な命令の削除、レジスタ割り当ての最適化、ループの展開など)を行います。

このコミットの文脈では、オプティマイザが関数呼び出しの際に、どのレジスタが呼び出し後も有効な値を保持している必要があるか(「ライブである」)という情報を利用して、より効率的なコードを生成することが意図されています。

### オブジェクトファイルとアセンブリリスト

*   **オブジェクトファイル**: コンパイラがソースコードをコンパイルした結果生成される中間ファイルです。まだリンカによって結合されていない、機械語コードとシンボル情報(関数名、変数名など)が含まれています。
*   **アセンブリリスト**: ソースコードがどのようにアセンブリ言語に変換されたかを示すリストです。デバッグやパフォーマンス分析のために、開発者が機械語レベルの動作を理解するのに役立ちます。

リンカの `-a` や `-W` オプションは、リンカの動作に関する詳細な情報や警告を出力するために使用されることがあります。

## 技術的詳細

この変更の核心は、リンカが `CALL` 命令の新しい形式 `CALL reg, reg` を認識し、処理できるようにすることです。従来の `CALL` 命令は通常、単一のオペランド(呼び出し先のアドレス)を取りますが、この拡張により、2つのレジスタをオペランドとして取ることが可能になります。

コミットメッセージによると、`CALL r1, r2` の形式において、`r1` は「src (source) 引数」と呼ばれ、`r2` は「dest (destination) 引数」または単に呼び出し先のアドレスを指すレジスタとして機能します。

重要な点は以下の通りです。

1.  **リンカの動作**: リンカ (`5l`, `6l`, `8l`) は、この新しい `src` 引数 (`r1`) を**無視**します。つまり、リンカの視点からは `CALL r1, r2` は `CALL r2` と全く同じように扱われます。リンカは、`r2` が指すアドレスへのジャンプ命令を生成するだけです。
2.  **オプティマイザへのヒント**: `src` 引数 (`r1`) の真の目的は、コンパイラ (`5g`, `6g`, `8g`) のオプティマイザへのヒントとして機能することです。このヒントは、「呼び出される関数にエントリする時点で、`r1` レジスタがライブであり、その値が呼び出し後も保持される必要がある」ということを示します。これは、特にGoのガベージコレクタがポインタを正確に追跡するために、どのレジスタが有効なポインタを含んでいるかを把握する必要がある場合に重要です。
3.  **デバッグと可視性**: この変更は、オブジェクトファイルやアセンブリリスト、リンカの出力に `CALL reg, reg` の形式が明示的に現れることを可能にします。これにより、開発者は、特定の `CALL` 命令がGoのランタイムが生成する「関数値呼び出し」(Goの `func` 型の変数を介した呼び出し)なのか、それともC言語のライブラリ関数などへの「C関数ポインタ呼び出し」なのかを区別できるようになります。この区別は、デバッグやパフォーマンス分析において非常に有用です。例えば、Goの関数値呼び出しは、通常、呼び出し規約やスタックフレームの管理において、C関数ポインタ呼び出しとは異なる特性を持つため、この情報が可視化されることで、より深いレベルでの理解と問題解決が可能になります。

### `optab.c` と `ycall`

Goのリンカのソースコードにおいて、`optab.c` ファイルは、各命令(オペコード)の処理方法を定義するテーブルを含んでいます。このテーブルは、リンカがアセンブリ命令を解析し、対応する機械語コードを生成するために使用されます。

`ycall` は、`CALL` 命令に関連するオペランドの型や処理方法を定義する配列(または構造体)であると推測されます。この配列は、リンカが `CALL` 命令に続くオペランドの組み合わせをどのように解釈し、検証するかを決定します。

変更点を見ると、`ycall` 配列に新しいエントリが追加されています。

*   `Ynone, Yml, Zo_m64, 2,` の行が `Ynone, Yml, Zo_m64, 0,` に変更され、さらに `Yrx, Yrx, Zo_m64, 2,` が追加されています。
    *   `Ynone`: オペランドなし
    *   `Yml`: メモリまたはレジスタのオペランド
    *   `Yrx`: レジスタのオペランド
    *   `Zo_m64`: 64ビットのメモリオペランド
    *   `Zo_m`: 32ビットのメモリオペランド
    *   最後の数値 (`2` や `0`) は、命令のバイト長やその他のフラグに関連している可能性があります。

この変更は、リンカが `CALL` 命令に対して、`Yrx, Yrx` の組み合わせ、つまり2つのレジスタオペランドを持つ形式を新たに認識し、処理できるようにするためのものです。

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

```diff
diff --git a/src/cmd/5l/optab.c b/src/cmd/5l/optab.c
index 1e93a3113b..231071f209 100644
--- a/src/cmd/5l/optab.c
+++ b/src/cmd/5l/optab.c
@@ -63,6 +63,7 @@ Optab	optab[] =
 
 	{ AB,		C_NONE,	C_NONE,	C_ROREG,	 6, 4, 0,	LPOOL },
 	{ ABL,		C_NONE,	C_NONE,	C_ROREG,	 7, 8, 0 },
+	{ ABL,		C_REG,	C_NONE,	C_ROREG,	 7, 8, 0 },
 	{ ABX,		C_NONE,	C_NONE,	C_ROREG,	 75, 12, 0 },
 	{ ABXRET,	C_NONE,	C_NONE,	C_ROREG,	 76, 4, 0 },
 
diff --git a/src/cmd/6l/optab.c b/src/cmd/6l/optab.c
index 43f34d9747..21b4784353 100644
--- a/src/cmd/6l/optab.c
+++ b/src/cmd/6l/optab.c
@@ -326,7 +326,8 @@ uchar	yloop[] =
 };
 uchar	ycall[] =
 {
-\tYnone,\tYml,\tZo_m64,\t2,\n+\tYnone,\tYml,\tZo_m64,\t0,\n+\tYrx,\tYrx,\tZo_m64,\t2,\n \tYnone,\tYbr,\tZcall,\t1,\n \t0
 };
diff --git a/src/cmd/8l/optab.c b/src/cmd/8l/optab.c
index ae808ec770..3ccdbfd226 100644
--- a/src/cmd/8l/optab.c
+++ b/src/cmd/8l/optab.c
@@ -266,7 +266,8 @@ uchar	yloop[] =
 };
 uchar	ycall[] =
 {
-\tYnone,\tYml,\tZo_m,\t2,\n+\tYnone,\tYml,\tZo_m,\t0,\n+\tYrx,\tYrx,\tZo_m,\t2,\n \tYnone,\tYcol,\tZcallind,\t2,\n \tYnone,\tYbr,\tZcall,\t0,\n \tYnone,\tYi32,\tZcallcon,\t1,\n```

## コアとなるコードの解説

### `src/cmd/5l/optab.c` の変更

ARMアーキテクチャ向けのリンカ (`5l`) の `optab.c` では、`ABL` (Branch with Link) 命令の定義に新しいエントリが追加されています。

```c
	{ ABL,		C_NONE,	C_NONE,	C_ROREG,	 7, 8, 0 },
+	{ ABL,		C_REG,	C_NONE,	C_ROREG,	 7, 8, 0 },
  • ABL: ARMの Branch with Link 命令。これは関数呼び出しに使用され、呼び出し元のアドレスをリンクレジスタ (LR) に保存してからジャンプします。
  • C_NONE: オペランドなし。
  • C_REG: レジスタオペランド。
  • C_ROREG: 読み出し専用レジスタオペランド。

追加された行は、ABL 命令が C_REG (ソースレジスタ) と C_ROREG (デスティネーションレジスタ) の2つのレジスタオペランドを取る形式をサポートすることを示しています。これにより、ABL Rsrc, Rdest のような形式の命令がリンカによって正しく解析されるようになります。

src/cmd/6l/optab.c の変更

AMD64アーキテクチャ向けのリンカ (6l) の optab.c では、ycall 配列が変更されています。

uchar	ycall[] =
{
-\tYnone,\tYml,\tZo_m64,\t2,\n+\tYnone,\tYml,\tZo_m64,\t0,\n+\tYrx,\tYrx,\tZo_m64,\t2,\n \tYnone,\tYbr,\tZcall,\t1,\n \t0
};
  • Ynone: オペランドなし。
  • Yml: メモリまたはレジスタのオペランド。
  • Yrx: レジスタのオペランド。
  • Zo_m64: 64ビットのメモリオペランド。

変更前は Ynone, Yml, Zo_m64, 2, というエントリがありましたが、これが Ynone, Yml, Zo_m64, 0, に変更され、さらに Yrx, Yrx, Zo_m64, 2, という新しいエントリが追加されています。 これは、CALL 命令が以下の2つの形式をサポートすることを示しています。

  1. Ynone, Yml, Zo_m64: 従来の CALL 命令で、単一のメモリまたはレジスタオペランド(呼び出し先アドレス)を取る形式。最後の数値が 2 から 0 に変更されていますが、これは命令のバイト長やリンカ内部のフラグに関連する可能性があります。
  2. Yrx, Yrx, Zo_m64: 新しく追加された形式で、2つのレジスタオペランド (Yrx, Yrx) を取る CALL 命令をサポートします。これは CALL Rsrc, Rdest の形式に対応し、Zo_m64 は64ビットのオペランドサイズを示唆しています。

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

386アーキテクチャ向けのリンカ (8l) の optab.c でも、ycall 配列が同様に変更されています。

uchar	ycall[] =
{
-\tYnone,\tYml,\tZo_m,\t2,\n+\tYnone,\tYml,\tZo_m,\t0,\n+\tYrx,\tYrx,\tZo_m,\t2,\n \tYnone,\tYcol,\tZcallind,\t2,\n \tYnone,\tYbr,\tZcall,\t0,\n \tYnone,\tYi32,\tZcallcon,\t1,\n```

*   `Zo_m`: 32ビットのメモリオペランド。

`6l` と同様に、`Ynone, Yml, Zo_m, 2,` が `Ynone, Yml, Zo_m, 0,` に変更され、`Yrx, Yrx, Zo_m, 2,` が追加されています。これは、386アーキテクチャにおいても、`CALL` 命令が2つのレジスタオペランドを取る形式をサポートするように拡張されたことを意味します。`Zo_m` は32ビットのオペランドサイズに対応しています。

これらの変更により、Goのリンカは、コンパイラが生成する `CALL reg, reg` 形式の命令を正しく解釈し、最終的な実行可能ファイルに含めることができるようになります。これにより、コンパイラはより詳細なレジスタのライブネス情報をリンカに伝え、デバッグ情報の可視性を向上させることが可能になります。

## 関連リンク

*   Go言語のコンパイラとリンカに関する公式ドキュメント (当時のもの): [https://golang.org/doc/](https://golang.org/doc/) (現在のドキュメントは変更されている可能性があります)
*   Go言語のツールチェーンの歴史と進化に関する議論: Goのメーリングリストやデザインドキュメントなどを参照。

## 参考にした情報源リンク

*   Go言語の公式ドキュメント (現在のもの): [https://go.dev/doc/](https://go.dev/doc/)
*   Go言語のソースコード (GitHub): [https://github.com/golang/go](https://github.com/golang/go)
*   アセンブリ言語の `CALL` 命令に関する一般的な情報 (例: Wikipedia, CPUアーキテクチャのドキュメント)
*   コンパイラ最適化に関する一般的な情報 (例: コンパイラ設計の教科書)
*   コミットに記載されている `https://golang.org/cl/7364045` は、Goのコードレビューシステム (Gerrit) のチェンジリストへのリンクです。これは、このコミットがマージされるまでの議論やレビューの履歴を確認するために非常に有用です。
*   Go language cmd/5l cmd/6l cmd/8l linker に関するWeb検索結果
    *   [https://go.dev/](https://go.dev/)
    *   [https://medium.com/](https://medium.com/)
    *   [https://go.googlesource.com/](https://go.googlesource.com/)
    *   [https://stackoverflow.com/](https://stackoverflow.com/)
    *   [https://altoros.com/](https://altoros.com/)