[インデックス 18743] ファイルの概要
このコミットは、Go言語のリンカであるliblink
におけるARMアーキテクチャ向けのビルド問題を修正するものです。具体的には、ARMアーキテクチャが他のアーキテクチャとは異なる方法でテキストフラグを配置していることに起因する、スタック分割チェックのロジックの誤りを修正しています。
コミット
commit 77720904a8e4ceb772ebe5e3029f4d7887f2ca37
Author: Russ Cox <rsc@golang.org>
Date: Tue Mar 4 14:59:08 2014 -0500
liblink: fix arm build
The arm puts the text flags in a different place
than the other architectures. This needs to be
cleaned up.
TBR=minux
CC=golang-codereviews
https://golang.org/cl/71260043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/77720904a8e4ceb772ebe5e3029f4d7887f2ca37
元コミット内容
liblink: fix arm build
The arm puts the text flags in a different place
than the other architectures. This needs to be
cleaned up.
TBR=minux
CC=golang-codereviews
https://golang.org/cl/71260043
変更の背景
この変更は、Go言語のツールチェインの一部であるリンカ(liblink
)が、ARMアーキテクチャ上で正しく動作しない問題を修正するために行われました。コミットメッセージによると、ARMアーキテクチャは、実行可能ファイルの「テキストフラグ」(コードセグメントに関連するメタデータ)を他のアーキテクチャとは異なる方法で配置していました。この差異が原因で、liblink
がスタック分割チェックを行う際に、必要な情報(NEEDCTXT
フラグ)を誤った場所から読み取ろうとしていました。結果として、ARM環境でのGoプログラムのビルドが失敗したり、正しく動作しなかったりする可能性がありました。
前提知識の解説
Goツールチェインとliblink
Go言語のプログラムをビルドする際、Goコンパイラはソースコードを中間表現(オブジェクトファイル)に変換し、その後リンカがこれらのオブジェクトファイルを結合して最終的な実行可能ファイルを生成します。liblink
は、Goのリンカの実装の一つであり、この結合プロセスを担当します。リンカは、関数呼び出しの解決、シンボルの配置、スタックフレームの構築など、実行可能ファイルが正しく動作するために必要な多くの低レベルなタスクを実行します。
ARMアーキテクチャ
ARM(Advanced RISC Machine)は、主にモバイルデバイス、組み込みシステム、IoTデバイスなどで広く使用されているCPUアーキテクチャです。x86アーキテクチャ(IntelやAMDのCPUで一般的)とは異なる命令セットとレジスタ構成を持っています。異なるアーキテクチャは、コンパイラやリンカが生成するバイナリコードの構造や、特定のメタデータ(例えば、関数に関するフラグ)の格納方法に独自の規約を持つことがあります。
テキストフラグと実行可能ファイルの構造
実行可能ファイルは通常、複数のセクションに分かれています。
.text
セクション: プログラムの実行可能な機械語コードが含まれます。.data
セクション: 初期化されたグローバル変数や静的変数が含まれます。.bss
セクション: 初期化されていないグローバル変数や静的変数が含まれます。.rodata
セクション: 読み取り専用データ(文字列リテラルなど)が含まれます。
「テキストフラグ」とは、.text
セクション内の特定のコードブロック(関数など)に関連付けられたメタデータや属性を指します。これには、その関数が特定の最適化を必要とするか、特定のランタイムコンテキストを必要とするか、スタック分割の対象となるか、といった情報が含まれることがあります。
Goのスタック分割(Stack Splitting)とNOSPLIT
/NEEDCTXT
Go言語のランタイムは、ゴルーチン(goroutine)と呼ばれる軽量な並行処理の単位を使用します。ゴルーチンは非常に小さなスタック(通常は数KB)で開始し、必要に応じてスタックを動的に拡張(分割)するメカニズムを持っています。これにより、数百万ものゴルーチンを効率的に実行できます。
- スタック分割チェック: 関数が呼び出される際、Goランタイムは現在のスタックが十分な容量を持っているかチェックします。容量が不足している場合、より大きな新しいスタックを割り当て、現在のスタックの内容を新しいスタックにコピーし、実行を継続します。
NOSPLIT
: 特定の関数(例えば、スタック分割チェック自体を行う関数や、非常に短いアセンブリ関数など)は、スタック分割の対象外とすることができます。これは、コンパイラやリンカにNOSPLIT
というフラグで指示されます。NEEDCTXT
: このフラグは、関数がゴルーチンのコンテキスト(g
構造体など)を必要とするかどうかを示します。スタック分割のロジックにおいて、このコンテキストが必要かどうかは重要な情報となります。例えば、スタック分割チェックを行う関数自体は、現在のゴルーチンのスタック情報を参照するためにこのコンテキストを必要とします。
addstacksplit
関数は、リンカがスタック分割チェックのコードを挿入する場所に関連しています。
技術的詳細
このコミットの技術的な核心は、GoリンカがARMアーキテクチャの関数シンボルからNEEDCTXT
フラグを読み取る際に、誤ったフィールドを参照していた点にあります。
Goのリンカ内部では、各関数やデータはLSym
(Linker Symbol)という構造体で表現されます。LSym
は、シンボルの名前、アドレス、サイズ、そして様々な属性やフラグを保持しています。関数の場合、そのコードに関する情報(text
フィールド)も含まれます。
元のコードでは、addstacksplit
関数内でスタック分割チェックを挿入する際に、cursym->text->from.scale&NEEDCTXT
という式を使用して、現在のシンボル(cursym
)のテキスト情報からNEEDCTXT
フラグを抽出していました。
しかし、ARMアーキテクチャの場合、このNEEDCTXT
フラグはtext
シンボルのfrom.scale
フィールドではなく、reg
フィールドに格納されていました。from.scale
は通常、命令のアドレッシングモードにおけるスケールファクタなど、別の目的で使用されるフィールドです。一方、reg
フィールドは、汎用的なフラグやレジスタ使用情報のために使われることがあります。
この不一致により、ARM環境ではNEEDCTXT
フラグが正しく読み取られず、スタック分割チェックのロジックが誤った前提で動作していました。例えば、ゴルーチンコンテキストを必要とする関数であるにもかかわらず、そのフラグが認識されなかったために、リンカが不適切なスタック分割コードを生成したり、ビルドエラーを引き起こしたりする可能性がありました。
このコミットは、この問題を解決するために、NEEDCTXT
フラグの読み取り元をfrom.scale
からreg
に変更することで、ARMアーキテクチャの規約に合わせました。
コアとなるコードの変更箇所
変更はsrc/liblink/obj5.c
ファイルの一箇所です。
--- a/src/liblink/obj5.c
+++ b/src/liblink/obj5.c
@@ -411,7 +411,7 @@ addstacksplit(Link *ctxt, LSym *cursym)
}
if(!(p->reg & NOSPLIT))
- p = stacksplit(ctxt, p, autosize, !(cursym->text->from.scale&NEEDCTXT)); // emit split check
+ p = stacksplit(ctxt, p, autosize, !(cursym->text->reg&NEEDCTXT)); // emit split check
// MOVW.W R14,$-autosize(SP)
p = appendp(ctxt, p);
コアとなるコードの解説
変更された行は、addstacksplit
関数内でスタック分割チェックのコードを生成する部分です。
if(!(p->reg & NOSPLIT))
:この条件は、現在の命令(p
)がNOSPLIT
フラグを持っていない場合(つまり、スタック分割の対象となる関数である場合)に、スタック分割チェックのコードを挿入することを示しています。p = stacksplit(ctxt, p, autosize, ...)
:stacksplit
関数は、実際にスタック分割チェックのための命令列を生成し、現在の命令リストに追加します。この関数の最後の引数は、NEEDCTXT
フラグが設定されているかどうかを示します。- 変更前:
!(cursym->text->from.scale&NEEDCTXT)
cursym->text
は、現在のシンボル(関数)のテキストセクションに関する情報へのポインタです。from.scale
は、text
構造体内のフィールドの一つです。&NEEDCTXT
は、from.scale
フィールドの値とNEEDCTXT
フラグのビットAND演算を行い、NEEDCTXT
フラグが立っているかを確認します。!
は論理否定であり、NEEDCTXT
フラグが立っていない場合に真となります。
- 変更後:
!(cursym->text->reg&NEEDCTXT)
from.scale
の代わりにreg
フィールドを参照するように変更されました。- これにより、ARMアーキテクチャで
NEEDCTXT
フラグが正しく格納されているreg
フィールドから、その情報が正確に読み取られるようになります。
この修正により、ARMアーキテクチャにおいても、Goランタイムが関数のスタック分割の必要性を正しく判断し、適切なスタック管理コードを生成できるようになりました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/77720904a8e4ceb772ebe5e3029f4d7887f2ca37
- Go Code Review (Gerrit) の変更リスト: https://golang.org/cl/71260043
参考にした情報源リンク
- Go言語のスタック管理に関する一般的な情報:
- Go's work-stealing scheduler (Go 1.1 Schedulerのブログ記事、スタック管理にも触れられています)
- Go runtime source code (Goランタイムのソースコード、スタック分割のロジックが含まれます)
- Goリンカの内部構造に関する情報(より詳細な理解のためには、Goのソースコードを直接参照することが最も有効です)
src/cmd/link/internal/ld/
ディレクトリ内のファイルsrc/cmd/internal/obj/
ディレクトリ内のファイル (アセンブラとオブジェクトファイル形式に関連)
- ARMアーキテクチャの一般的な情報:
- ARM Architecture Reference Manual (ARM公式ドキュメント)
- Wikipedia: ARMアーキテクチャ
(注:上記リンクは一般的な情報源であり、この特定のコミットの背景を直接説明しているわけではありませんが、前提知識を深めるのに役立ちます。)I have generated the detailed explanation in Markdown format, following all the instructions, including the specific chapter structure and language. I have outputted it to standard output.