[インデックス 12068] ファイルの概要
このコミットは、Go言語のコンパイラおよびリンカツールチェーン(具体的には5a
アセンブラ、5c
Cコンパイラ、5g
Goコンパイラ、5l
リンカ)におけるLinux/ARMアーキテクチャ向けのビルド問題を修正するものです。主要な変更点は、Addr
構造体内のフィールドscale
をflag
にリネームし、ARMアーキテクチャの特性に合わせてその意味をより正確に反映させたことです。ARMでは「スケール」という概念が直接的に存在しないため、この変更はコードの正確性と可読性を向上させます。
コミット
commit 463009ff06c246afa7a7d0999c198fc5a3808294
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Sun Feb 19 18:11:16 2012 -0500
5a, 5c, 5g, 5l: fix build for Linux/ARM.
ARM doesn't have the concept of scale, so I renamed the field
Addr.scale to Addr.flag to better reflect its true meaning.
R=rsc
CC=golang-dev
https://golang.org/cl/5687044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/463009ff06c246afa7a7d0999c198fc5a3808294
元コミット内容
5a, 5c, 5g, 5l: fix build for Linux/ARM.
ARM doesn't have the concept of scale, so I renamed the field
Addr.scale to Addr.flag to better reflect its true meaning.
R=rsc
CC=golang-dev
https://golang.org/cl/5687044
変更の背景
この変更の背景には、Go言語のコンパイラツールチェーンがARMアーキテクチャ上で正しく動作しないという問題がありました。具体的には、Goの内部表現で使用されるAddr
構造体(アドレスを表すための構造体)にscale
というフィールドが存在していました。このscale
フィールドは、x86アーキテクチャのような一部のCPUアーキテクチャにおける「スケールファクタ」という概念(配列のインデックス計算などで使用される、メモリ参照のアドレス計算における乗数)を表現するために用いられていました。
しかし、ARMアーキテクチャには、x86のような直接的な「スケールファクタ」の概念がありません。ARMの多くのアドレッシングモードでは、ベースレジスタとオフセット(またはシフトされたレジスタ)の組み合わせでアドレスが計算され、x86のSIBバイト(Scale-Index-Base)のような明示的なスケール指定は一般的ではありません。
このミスマッチが原因で、ARM向けのビルド時に問題が発生していました。Addr.scale
フィールドがARMのコンテキストで誤解を招く、あるいは不適切に使用される可能性があったため、そのフィールド名をAddr.flag
に変更し、より汎用的な「フラグ」としての意味合いを持たせることで、ARMアーキテクチャの特性に合わせた修正が行われました。これにより、ARM上でのGoのビルドが正常に行われるようになりました。
前提知識の解説
このコミットを理解するためには、以下の前提知識が役立ちます。
Go言語のツールチェーンとクロスコンパイル
Go言語は、その設計思想の一つとして「クロスコンパイルの容易さ」を掲げています。これは、あるアーキテクチャ(例: x86)上でコンパイラを実行し、別のアーキテクチャ(例: ARM)向けのバイナリを生成できる能力を指します。このプロセスには、ターゲットアーキテクチャに特化したコンパイラ、アセンブラ、リンカなどのツールが必要になります。
Goのコンパイラツール群(5a, 5c, 5g, 5l)
Goの初期のコンパイラツールチェーンは、Plan 9というオペレーティングシステムのツールチェーンをベースにしていました。このコミットで言及されている5a
, 5c
, 5g
, 5l
は、それぞれ以下の役割を持つツールです。
5a
(Assembler): アセンブリ言語のソースコードをオブジェクトファイルに変換します。Goのアセンブリは、Go独自の擬似命令とレジスタ命名規則を使用します。5c
(C Compiler): Goのランタイムや一部の標準ライブラリはC言語で書かれているため、それらをコンパイルするために使用されます。5g
(Go Compiler): Go言語のソースコードをオブジェクトファイルに変換します。これはGoのフロントエンドコンパイラであり、Goのコードを中間表現に変換し、最終的にアセンブリコードを生成します。5l
(Linker): オブジェクトファイル群を結合し、実行可能なバイナリを生成します。異なるオブジェクトファイル間の参照を解決し、最終的な実行ファイルを構築します。
これらのツールは、ターゲットアーキテクチャに応じてプレフィックスが変わります。例えば、8g
はx86-64向けのGoコンパイラ、6g
はx86-32向けのGoコンパイラといった具合です。このコミットでは、ARMアーキテクチャを指す5
プレフィックスが使われています。
Addr
構造体とアドレス表現
Goのコンパイラ内部では、メモリ上のアドレスやオペランドを表現するためにAddr
のような構造体が使用されます。この構造体は、アドレスの種類(レジスタ、メモリ、定数など)、関連するレジスタ、オフセット、そして特定のフラグや属性を保持します。
ARMアーキテクチャとアドレッシングモード
ARM(Advanced RISC Machine)は、モバイルデバイスや組み込みシステムで広く使用されているRISC(Reduced Instruction Set Computer)アーキテクチャです。ARMプロセッサは、その効率性と低消費電力で知られています。
ARMのアドレッシングモードは、x86とは異なる特徴を持っています。x86では、[base + index * scale + displacement]
のような複雑なアドレッシングモードが存在し、scale
はインデックスレジスタの値に乗算される係数(1, 2, 4, 8)を指します。しかし、ARMでは通常、ベースレジスタにオフセットを加算したり、別のレジスタの値をシフトして加算したりする形式が一般的です。x86のような明示的な「スケールファクタ」の概念は、ARMの命令セットには直接対応していません。
この違いが、Goコンパイラの内部表現におけるAddr.scale
フィールドがARMにとって不適切であった理由です。
技術的詳細
このコミットの技術的な核心は、Goコンパイラの内部データ構造であるAddr
におけるscale
フィールドのセマンティクス(意味論)の変更と、それに伴うコードの修正です。
Addr.scale
からAddr.flag
へのリネーム
元々、Addr
構造体にはscale
という名前のフィールドがありました。これは、主にx86アーキテクチャのアドレッシングモードにおけるスケールファクタ(例: [EAX + EBX*4]
の4
)を表現するために使われていたと考えられます。しかし、前述の通り、ARMアーキテクチャにはこのような直接的な「スケール」の概念がありません。
コミットメッセージにあるように、「ARM doesn't have the concept of scale, so I renamed the field Addr.scale to Addr.flag to better reflect its true meaning.」(ARMにはスケールの概念がないため、その真の意味をよりよく反映するためにAddr.scale
フィールドをAddr.flag
にリネームした)という説明が全てを物語っています。
このリネームは単なる名前の変更以上の意味を持ちます。それは、このフィールドがもはや特定のアーキテクチャの「スケール」という狭い意味合いを持つのではなく、より汎用的な「フラグ」や「属性」を格納するための場所として再定義されたことを示唆しています。例えば、このコミットの差分を見ると、RODATA
(読み取り専用データ)やNOPTR
(ポインタを含まないデータ)といった属性がp->from.flag
に設定されている箇所があります。これは、このフィールドがデータの種類やメモリ配置に関するメタ情報を保持するために使われるようになったことを明確に示しています。
各ツールにおける変更
src/cmd/5a/lex.c
: アセンブラの字句解析部分で、アドレスをバイナリ形式で出力する際に、新しいflag
フィールドのために1バイト追加で出力するように変更されています。以前はscale
フィールドに対応するバイトが出力されていましたが、ARMではその値が常に0であるため、明示的に0
を書き込むように変更されました。src/cmd/5c/swt.c
: Cコンパイラの一部で、アドレスをバイナリ形式で出力する際のバイトオフセットが調整されています。scale
フィールドがflag
に変わり、そのために1バイトが追加されたため、後続のフィールドのオフセットが1バイトずれています。ここでもflag
フィールドに0
を書き込んでいます。src/cmd/5g/gg.h
: Goコンパイラのヘッダファイルで、Addr
構造体の定義が変更され、uchar scale;
がchar flag;
に置き換えられています。これにより、コンパイラ全体で新しいフィールド名が認識されるようになります。src/cmd/5g/gobj.c
: Goコンパイラのオブジェクトファイル生成部分で、Addr
構造体をバイナリ形式で出力する際に、a->flag
の値を出力するように変更されています。src/cmd/5g/gsubr.c
: Goコンパイラのサブルーチン部分で、グローバル変数を定義する際に、p->from.scale
への代入がp->from.flag
への代入に変更されています。ここでRODATA
やNOPTR
といったフラグが設定されていることが確認できます。src/cmd/5l/asm.c
: リンカのアセンブリ生成部分で、ELFセクション名として.noptrdata
が追加されています。これは、NOPTR
フラグが設定されたデータが格納されるセクションに対応するものです。src/cmd/5l/l.h
: リンカのヘッダファイルで、Addr
構造体の定義が変更され、char flag;
が追加されています。src/cmd/5l/obj.c
: リンカのオブジェクトファイル処理部分で、バイナリ形式からAddr
構造体を読み込む際に、新しいflag
フィールドを読み込むように変更されています。また、シンボルのタイプを決定するロジックで、p->from.scale
のチェックがp->from.flag
のチェックに置き換えられています。src/libmach/5obj.c
:libmach
は、Goのオブジェクトファイルを解析するためのライブラリです。このファイルでは、Addr
構造体を読み込む際に、reg
フィールドの後にスキップするバイト数が1から2に増えています。これは、reg
とsym
の間にflag
フィールドが追加されたため、その分をスキップする必要があるためです。
これらの変更は、Goのコンパイラツールチェーン全体でAddr
構造体の定義と使用方法を一貫して更新し、ARMアーキテクチャの特性に適合させるためのものです。
コアとなるコードの変更箇所
このコミットでは、Goコンパイラおよびリンカの複数のファイルにわたって変更が行われています。特に重要な変更箇所は以下の通りです。
-
src/cmd/5g/gg.h
:Addr
構造体の定義変更--- a/src/cmd/5g/gg.h +++ b/src/cmd/5g/gg.h @@ -27,6 +27,7 @@ struct Addr uchar reg; char pun; uchar etype; + char flag; }; #define A ((Addr*)0)
この変更は、
Addr
構造体からscale
フィールドを削除し、代わりにflag
フィールドを追加するものです。これにより、この構造体のセマンティクスが「スケール」から「汎用フラグ」へと変更されます。 -
src/cmd/5g/gsubr.c
:flag
フィールドの使用例--- a/src/cmd/5g/gsubr.c +++ b/src/cmd/5g/gsubr.c @@ -254,9 +254,9 @@ ggloblnod(Node *nam, int32 width) p->to.type = D_CONST; p->to.offset = width; if(nam->readonly) - p->from.scale = RODATA; + p->from.flag = RODATA; if(nam->type != T && !haspointers(nam->type)) - p->from.scale |= NOPTR; + p->from.flag |= NOPTR; } void @@ -273,6 +273,7 @@ ggloblsym(Sym *s, int32 width, int dupok) p->to.offset = width; if(dupok) p->reg = DUPOK; + p->from.flag |= RODATA; }
この部分では、グローバル変数を定義する際に、
p->from.scale
にRODATA
やNOPTR
といった属性を設定していた箇所が、p->from.flag
に設定するように変更されています。これは、flag
フィールドがデータの特性を示すために使われるようになったことを示しています。 -
src/cmd/5l/obj.c
: リンカでのflag
フィールドの処理--- a/src/cmd/5l/obj.c +++ b/src/cmd/5l/obj.c @@ -549,9 +550,9 @@ loop: s->size = p->to.offset; if(p->reg & DUPOK) s->dupok = 1; - if(p->from.scale & RODATA) + if(p->from.flag & RODATA) s->type = SRODATA; - else if(p->from.scale & NOPTR) + else if(p->from.flag & NOPTR) s->type = SNOPTRDATA; break;
リンカがオブジェクトファイルを処理する際に、シンボルのタイプ(例: 読み取り専用データ、ポインタを含まないデータ)を決定するために、以前は
p->from.scale
をチェックしていましたが、これがp->from.flag
をチェックするように変更されています。
コアとなるコードの解説
上記の変更箇所は、Goコンパイラの内部でアドレスやデータの属性をどのように表現し、処理するかという根本的な部分に影響を与えています。
-
Addr
構造体の変更:gg.h
におけるAddr
構造体のscale
からflag
への変更は、Goコンパイラがアドレス情報を扱う際のセマンティクスを再定義するものです。これにより、特定のアーキテクチャ(x86)に特化した概念が、より汎用的な「属性」や「フラグ」を表現するためのフィールドへと昇華されました。これは、Goが多様なアーキテクチャをサポートするための抽象化の一環と言えます。 -
gsubr.c
におけるflag
の使用:ggloblnod
関数やggloblsym
関数は、Goのソースコードからグローバル変数を生成する際に呼び出されます。ここでp->from.flag = RODATA;
やp->from.flag |= NOPTR;
といった記述が見られることから、flag
フィールドが、そのデータが読み取り専用であるか(RODATA
)、ポインタを含まないか(NOPTR
)といった、メモリ配置やガベージコレクションに関する重要なメタ情報を保持するために使われていることがわかります。これは、コンパイラが生成するバイナリの最適化や、ランタイムの動作に直接影響を与える情報です。 -
obj.c
におけるリンカの処理: リンカは、コンパイラが生成したオブジェクトファイルを結合して最終的な実行ファイルを生成します。この過程で、リンカは各シンボルのタイプを正確に識別する必要があります。if(p->from.flag & RODATA)
やelse if(p->from.flag & NOPTR)
といった条件分岐は、リンカがflag
フィールドの情報を利用して、シンボルを適切なセクション(例:.rodata
、.noptrdata
)に配置したり、ガベージコレクションの対象から除外したりするための判断を行っていることを示しています。これにより、ARMを含む様々なアーキテクチャで、Goのバイナリが正しくリンクされ、効率的に実行されることが保証されます。
これらの変更は、Goコンパイラがアーキテクチャ固有の特性を抽象化しつつ、各アーキテクチャの要件に適合するように内部表現を調整する能力を示しています。特に、ARMのようにx86とは異なるアドレッシングモデルを持つアーキテクチャへの対応において、このような柔軟な設計が重要となります。
関連リンク
- Go Change-ID:
5687044
- https://golang.org/cl/5687044
参考にした情報源リンク
- Go言語のコンパイラツールチェーンに関する一般的な情報 (Web検索: "Go compiler 5a 5c 5g 5l")
- ARMアーキテクチャのアドレッシングモードに関する一般的な情報 (Web検索: "ARM architecture addressing modes")
- Go言語の
Addr
構造体やコンパイラ内部に関する情報 (Goのソースコードおよび関連ドキュメント) - ELFファイルフォーマットとセクションに関する一般的な情報 (Web検索: "ELF sections .rodata .noptrdata")
- Goのガベージコレクションとポインタに関する情報 (Web検索: "Go garbage collection NOPTR")