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

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

このコミットは、Go言語のコンパイラおよびリンカツールチェーン(具体的には5aアセンブラ、5cCコンパイラ、5gGoコンパイラ、5lリンカ)におけるLinux/ARMアーキテクチャ向けのビルド問題を修正するものです。主要な変更点は、Addr構造体内のフィールドscaleflagにリネームし、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への代入に変更されています。ここでRODATANOPTRといったフラグが設定されていることが確認できます。
  • 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に増えています。これは、regsymの間にflagフィールドが追加されたため、その分をスキップする必要があるためです。

これらの変更は、Goのコンパイラツールチェーン全体でAddr構造体の定義と使用方法を一貫して更新し、ARMアーキテクチャの特性に適合させるためのものです。

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

このコミットでは、Goコンパイラおよびリンカの複数のファイルにわたって変更が行われています。特に重要な変更箇所は以下の通りです。

  1. 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フィールドを追加するものです。これにより、この構造体のセマンティクスが「スケール」から「汎用フラグ」へと変更されます。

  2. 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.scaleRODATANOPTRといった属性を設定していた箇所が、p->from.flagに設定するように変更されています。これは、flagフィールドがデータの特性を示すために使われるようになったことを示しています。

  3. 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言語のコンパイラツールチェーンに関する一般的な情報 (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")