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

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

このコミットは、Go言語のARMアーキテクチャ向けリンカである5lpatch()関数における最適化に関するものです。具体的には、シンボル解決の一般的なケースにおいて、不要なシンボル値の検索をスキップすることでパフォーマンスを向上させています。

コミット

commit 2d64bab1ded3c17d1e73ee1dcacf1aafae223317
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Tue Jan 31 10:59:34 2012 -0500

    5l: optimize the common case in patch()
        If p->to.sym->text is non-nil, then no need to search for sym->value.

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

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

https://github.com/golang/go/commit/2d66bab1ded3c17d1e73ee1dcacf1aafae223317

元コミット内容

5l: optimize the common case in patch() If p->to.sym->text is non-nil, then no need to search for sym->value.

このコミットメッセージは、5lリンカのpatch()関数における一般的なケースの最適化を目的としていることを示しています。具体的には、シンボル(p->to.sym)のtextフィールドがnilでない場合、つまりシンボルの実体(コードやデータ)が既に利用可能である場合、そのシンボルのvalue(アドレスやオフセット)を改めて検索する必要がない、という最適化です。

変更の背景

Go言語のコンパイラとリンカは、Goプログラムを効率的に実行可能なバイナリに変換するために重要な役割を担っています。リンカの主な役割の一つは、コンパイルされたオブジェクトファイルやライブラリを結合し、シンボル参照を解決して、最終的な実行可能ファイルを生成することです。このプロセスには、関数呼び出しやデータ参照のアドレスを正確に解決し、必要に応じてコードを修正(パッチ適用)する作業が含まれます。

5lはGoのARMアーキテクチャ向けリンカであり、組み込みシステムやモバイルデバイスなど、リソースが限られた環境でのパフォーマンスが特に重要です。リンカの処理速度は、開発サイクルやビルド時間に直接影響するため、可能な限り効率的であることが求められます。

このコミットの背景には、patch()関数がシンボル解決の過程で頻繁に呼び出されるため、その内部のわずかな非効率性が全体のリンク時間に大きな影響を与える可能性があるという認識があったと考えられます。特に、シンボルのtextフィールドが既に設定されている(つまり、シンボルの実体が既にメモリ上にロードされているか、その場所が確定している)場合、valueを再検索するような冗長な処理は避けるべきです。このような冗長な検索を排除することで、リンカのパフォーマンスを向上させ、ビルド時間を短縮することが目的とされています。

前提知識の解説

リンカの役割

リンカは、コンパイラによって生成された複数のオブジェクトファイル(.oファイルなど)やライブラリを結合し、単一の実行可能ファイルや共有ライブラリを生成するプログラムです。主な機能は以下の通りです。

  1. シンボル解決 (Symbol Resolution): プログラム内の関数や変数などのシンボル参照を、それらが定義されている実際のアドレスに解決します。例えば、main関数がfmt.Printlnを呼び出す場合、リンカはfmt.Printlnがメモリ上のどこにあるかを特定し、main関数内の呼び出し命令をそのアドレスに修正します。
  2. 再配置 (Relocation): オブジェクトファイルは通常、0番地からの相対アドレスでコードやデータを生成します。リンカは、これらの相対アドレスを最終的な実行可能ファイル内の絶対アドレスに変換し、必要に応じてコード内のアドレス参照を修正します。
  3. セクション結合 (Section Merging): オブジェクトファイル内のコードセクション(.text)、データセクション(.data)、BSSセクション(.bss)などを結合し、最終的な実行可能ファイルのメモリレイアウトを決定します。

Go言語のリンカ (cmd/5l, cmd/6l, cmd/8lなど)

Go言語のツールチェインには、各アーキテクチャ(ARM, AMD64, x86など)に対応する専用のリンカが含まれています。

  • 5l: ARMアーキテクチャ向けリンカ
  • 6l: AMD64アーキテクチャ向けリンカ
  • 8l: x86アーキテクチャ向けリンカ

これらのリンカは、Goのランタイムや標準ライブラリ、ユーザーコードをリンクするために使用されます。Goのリンカは、C言語のリンカ(ldなど)とは異なり、Goの特定の機能(例: goroutineのスタック管理、インターフェースのディスパッチなど)をサポートするために、よりGoランタイムと密接に連携しています。

patch()関数

リンカにおけるpatch()関数は、一般的に、シンボル解決や再配置の過程で、生成されるバイナリコード内の特定のアドレスや命令を修正(パッチ適用)するために使用されます。これは、例えば、関数呼び出しのターゲットアドレスを埋め込んだり、グローバル変数への参照を修正したりする際に必要となります。

シンボル (Sym) 構造体と text, value フィールド

リンカ内部では、プログラム内の各シンボル(関数、グローバル変数など)が内部的なデータ構造(例えばSym構造体)で表現されます。この構造体には、シンボルの名前、型、サイズ、そしてメモリ上の位置に関する情報が含まれます。

  • s->text: このフィールドは、シンボルがコードセクション(.text)に属する関数などの場合、そのシンボルの実体(コードブロック)へのポインタ、またはその実体がメモリ上でどこに配置されるかを示す情報を持つことがあります。nilでない場合は、シンボルの実体が既に確定していることを示唆します。
  • s->value: このフィールドは、シンボルの最終的なアドレス(オフセット)を表します。リンカがシンボル解決と再配置を完了した後に、この値が確定します。

このコミットの文脈では、s->textnilでない場合、それはシンボルが既に処理され、そのコードやデータがメモリ上のどこに配置されるかが確定していることを意味します。したがって、s->valueを改めて検索する必要がない、というロジックが適用されます。

技術的詳細

このコミットは、src/cmd/5l/pass.cファイル内のpatch()関数を修正しています。patch()関数は、リンカのパスの一部として、生成されるバイナリコード内の参照を修正する役割を担っています。

変更の核心は、条件分岐命令(ABL, ABX, AB, ARET)のターゲットシンボルを処理するロジックにあります。これらの命令は、通常、関数呼び出しやジャンプなど、プログラムの制御フローを変更するために使用されます。

元のコードでは、これらの命令のターゲットがシンボル(p->to.sym)であり、それがS(おそらく未定義シンボルを示す特別なシンボル)でない場合に、シンボルsを取得し、その型に応じて処理を行っていました。特にSTEXT(テキストセクションのシンボル、つまり関数)の場合、p->to.offsets->valueを設定し、p->to.typeD_BRANCHに設定していました。

今回の最適化は、このSTEXTケースの処理を改善しています。

  1. if(s->text == nil) continue; の追加: patch()関数がシンボルsを処理する際、まずs->textnilであるかどうかをチェックします。もしs->textnilであれば、そのシンボルはまだ完全に解決されていないか、その実体が確定していない状態であると判断し、現在の処理をスキップして次の命令のパッチ適用に進みます(continue)。これにより、未確定なシンボルに対して不要な処理を試みることを避けます。これは、リンカの複数パス処理において、シンボルが完全に解決される前にpatch()が呼び出される可能性がある場合に特に有効です。

  2. STEXTケースの変更:

    • p->to.offset = s->value; はそのまま残ります。これは、シンボルの最終的なアドレスオフセットを命令のターゲットオフセットに設定する重要なステップです。
    • p->to.type = D_BRANCH; もそのまま残ります。これは、ターゲットが分岐であることを示します。
    • p->cond = s->text; の追加: ここが重要な変更点です。p->condフィールドは、命令の条件コードや、場合によっては分岐ターゲットの追加情報(例えば、シンボルの実体へのポインタ)を保持するために使用されることがあります。s->textp->condに設定することで、シンボルの実体への直接的な参照を命令構造に埋め込むことができます。これにより、後続の処理でs->valueを再検索する手間を省くことができます。
    • break;continue; に変更: 元のコードではSTEXTケースの処理後にswitch文をbreakしていましたが、continueに変更されています。これは、STEXTケースの処理が完了したら、現在の命令のパッチ適用は完了し、次の命令の処理に進むべきであることを示唆しています。

この変更により、s->textが既に利用可能な場合、リンカはシンボルのvalueを再検索する代わりに、s->textを直接利用して効率的にパッチ適用を行うことができます。これは、特に大きなプログラムや多数のシンボルを持つプログラムにおいて、リンカの実行時間を短縮する効果が期待できます。

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

--- a/src/cmd/5l/pass.c
+++ b/src/cmd/5l/pass.c
@@ -213,6 +213,8 @@ patch(void)
 		if((a == ABL || a == ABX || a == AB || a == ARET) &&
 		   p->to.type != D_BRANCH && p->to.sym != S) {
 			s = p->to.sym;
+			if(s->text == nil)
+				continue;
 			switch(s->type) {
 			default:
 				diag("undefined: %s", s->name);
@@ -222,7 +224,8 @@ patch(void)
 			case STEXT:
 				p->to.offset = s->value;
 				p->to.type = D_BRANCH;
-				break;
+				p->cond = s->text;
+				continue;
 			}
 		}
 		if(p->to.type != D_BRANCH)

コアとなるコードの解説

変更はsrc/cmd/5l/pass.cファイルのpatch()関数内で行われています。

  1. 追加された行 (+で始まる行):

    +			if(s->text == nil)
    +				continue;
    

    このコードは、ABL, ABX, AB, ARETといった分岐命令のターゲットシンボルsを処理する直前に追加されました。もしシンボルstextフィールドがnil(ヌルポインタ)であれば、そのシンボルはまだ実体が確定していないか、処理の準備ができていないと判断し、現在の命令のパッチ適用をスキップして次の命令に移ります。これにより、未確定なシンボルに対する無駄な処理を回避し、リンカの効率を向上させます。

  2. 変更された行 (-+で始まる行):

    -				break;
    +				p->cond = s->text;
    +				continue;
    

    これはswitch(s->type)文のcase STEXT:ブロック内の変更です。

    • p->to.offset = s->value;p->to.type = D_BRANCH; はそのままです。これらは、分岐命令のターゲットオフセットとタイプを設定する基本的な処理です。
    • p->cond = s->text; が追加されました。これは、命令構造体pcondフィールドに、シンボルstextフィールドの値を代入しています。s->textは、シンボルの実体(コードブロック)へのポインタや、その実体がメモリ上のどこに配置されるかを示す情報を持つため、これをp->condに設定することで、後続の処理でシンボルの実体への直接的な参照を利用できるようになります。これにより、s->valueを再検索する手間を省き、処理を高速化します。
    • break;continue; に変更されました。breakswitch文を抜けるだけですが、continueは現在のforループ(patch関数内の命令をイテレートするループ)の次のイテレーションに進みます。これは、STEXTケースの処理が完了したら、現在の命令のパッチ適用は完了し、次の命令の処理に進むべきであることを明確に示しています。

これらの変更により、patch()関数は、シンボルのtextフィールドが既に利用可能な場合に、より効率的にシンボル参照を解決し、命令にパッチを適用できるようになります。

関連リンク

参考にした情報源リンク

  • Go言語のコミットメッセージとdiff: /home/orange/Project/comemo/commit_data/11509.txt
  • Go言語のリンカに関する一般的な情報 (Web検索結果に基づく)
    • Goのリンカの内部構造や動作に関するブログ記事やドキュメント (例: "Go linker internals", "Go toolchain")
    • コンパイラとリンカの基本的な概念に関するコンピュータサイエンスの教科書やオンラインリソース
  • Go CL 5601046: https://golang.org/cl/5601046 (このコミットの元のコードレビューページ)
  • Goのソースコード (src/cmd/5l/pass.cの関連部分)