[インデックス 17881] ファイルの概要
このコミットは、Go言語のgc
コンパイラスイート(6g
, 8g
など)が使用するアセンブリ言語に関する新しいドキュメントdoc/asm.html
を追加するものです。このドキュメントは、Goのアセンブラの特異な形式と、Goと連携するアセンブリコードを記述する際の注意点について、簡潔なガイドを提供します。
コミット
commit 2fbcb0819206775b919ca62eca961f94df06d0f4
Author: Rob Pike <r@golang.org>
Date: Tue Nov 12 20:04:22 2013 -0800
doc/asm.html: new document, a brief guide to the assembler
Fixes #6060
R=golang-dev, iant, bradfitz, josharian, minux.ma, aram, rsc
CC=golang-dev
https://golang.org/cl/20930043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2fbcb0819206775b919ca62eca961f94df06d0f4
元コミット内容
doc/asm.html: new document, a brief guide to the assembler
このコミットは、Goのアセンブラに関する新しいドキュメントdoc/asm.html
を追加します。このドキュメントは、Goのgc
コンパイラが使用するアセンブリ言語の独特な形式について、簡潔なガイドを提供することを目的としています。
変更の背景
Go言語は、そのランタイムや一部の標準ライブラリにおいて、パフォーマンスが重要な部分でアセンブリ言語を使用しています。しかし、Goのアセンブラは一般的なアセンブラとは異なり、Plan 9のアセンブラをベースにしているため、その構文や動作には特有の癖があります。特に、Goのコンパイラとリンカの連携方法により、アセンブリコードが直接的な機械語の表現ではないという点が重要です。
これまでのGoのドキュメントには、この特殊なアセンブラに関する包括的なガイドが存在せず、アセンブリコードを記述しようとする開発者にとっては学習コストが高い状況でした。このコミットは、Goのアセンブラの基本的な概念、構文、およびGoのランタイムとの相互作用に関するガイドを提供することで、このギャップを埋めることを目的としています。Fixes #6060
という記述から、このドキュメントの追加が、Goのアセンブラに関するドキュメント不足の問題(Issue 6060)を解決するためであることがわかります。
前提知識の解説
Goのアセンブラを理解するためには、以下の前提知識が役立ちます。
- アセンブリ言語の基本: CPUの命令セット、レジスタ、メモリ操作、スタック、関数呼び出し規約など、一般的なアセンブリ言語の概念。
- Plan 9オペレーティングシステム: Goのアセンブラは、Plan 9オペレーティングシステムのアセンブラに強く影響を受けています。Plan 9のアセンブラは、一般的なx86アセンブラとは異なる独自の構文と哲学を持っています。特に、命令のオペランドの順序(ソースからデスティネーションへ)や、擬似レジスタの概念がGoのアセンブラにも引き継がれています。
- Goコンパイラのアーキテクチャ: Goの
gc
コンパイラは、一般的なコンパイラとは異なり、アセンブラパスを通常のパイプラインに持ちません。代わりに、コンパイラは「不完全に定義された命令セット」をバイナリ形式で出力し、リンカがそれを完成させます。これにより、アセンブリコードに記述されたMOV
のような命令が、最終的にリンカによって異なる機械語命令(例:clear
やload
)に変換される可能性があります。これは、Goのアセンブラが「基盤となる機械の直接的な表現ではない」という重要な特性につながります。 - Goのランタイム: Goのランタイムは、ガベージコレクタ、スケジューラ、スタック管理など、Goプログラムの実行を支える重要なコンポーネントです。アセンブリコードは、これらのランタイム機能と密接に連携する必要があるため、
m
(マシン)やg
(ゴルーチン)構造体へのポインタの扱い、スタックの分割(stack splitting)などの概念を理解することが重要です。 - 擬似レジスタ: Goのアセンブラでは、
SB
(static base)やFP
(frame pointer)といった特殊な擬似レジスタが使用されます。これらは物理的なCPUレジスタではなく、メモリ上のアドレスやスタックフレーム内のオフセットを表現するために抽象的に使用されます。
技術的詳細
doc/asm.html
ドキュメントは、Goのアセンブラの以下の主要な技術的側面を詳細に説明しています。
-
Goアセンブラの抽象性:
- Goのアセンブラは、基盤となる機械の直接的な表現ではありません。これは、
gc
コンパイラが通常のパイプラインでアセンブラパスを必要としないためです。 - コンパイラは「不完全に定義された命令セット」をバイナリ形式で出力し、リンカが命令選択(instruction selection)を行います。例えば、
MOV
命令がリンカによってclear
やload
命令に変換されることがあります。 - 機械固有の操作はそのまま表現される傾向がありますが、メモリ移動やサブルーチン呼び出し/リターンといったより一般的な概念は抽象化されています。
- Goのアセンブラは、基盤となる機械の直接的な表現ではありません。これは、
-
シンボル (Symbols):
PC
,R0
,SP
などのレジスタは事前に宣言されています。SB
(static base): メモリの原点と考えることができる擬似レジスタです。foo(SB)
は、メモリ上のアドレスとしてのfoo
という名前を表します。FP
(frame pointer): 仮想的なフレームポインタです。関数への引数は、この擬似レジスタからのオフセットとして参照されます。例えば、0(FP)
は関数の最初の引数、8(FP)
は2番目の引数(64ビットマシン上)を表します。引数を名前で参照する場合、first_arg+0(FP)
のように記述しますが、この名前はセマンティックな意味を持たず、読者へのコメントとして機能します。- 命令、レジスタ、アセンブラディレクティブは、アセンブリプログラミングの難しさを強調するために、常に大文字で記述されます(ARMの
m
とg
レジスタの例外を除く)。 - Goのオブジェクトファイルやバイナリでは、シンボルの完全な名前はパッケージパスとピリオド、そしてシンボル名(例:
fmt.Printf
)です。アセンブラのパーサーはピリオドやスラッシュを句読点として扱うため、アセンブラソースファイル内では、中点文字U+00B7(·
)と除算スラッシュU+2215(∕
)を使用して、fmt·Printf
やmath∕rand·Int
のように記述します。 - 手書きのアセンブリファイルでは、リンカがカレントオブジェクトファイルのパッケージパスを、ピリオドで始まるシンボル名の先頭に挿入するため、完全なパッケージパスを含めないことが一般的です。例えば、
math/rand
パッケージの実装内のアセンブリソースファイルでは、·Int
と記述することで、パッケージのInt
関数を参照できます。
-
ディレクティブ (Directives):
TEXT
: 関数定義を宣言します。例:TEXT runtime·profileloop(SB),NOSPLIT,$8
。TEXT
ブロックの最後の命令は、通常RET
(擬似命令)のようなジャンプ命令でなければなりません。- 引数としてフラグとフレームサイズ(例:
$8
)を取ります。 - フレームサイズに続いて引数サイズを
-
で区切って記述することがあります(例:$24-8
)。これは減算ではなく、単なる構文です。NOSPLIT
が指定されていない場合、引数サイズは必須です。
DATA
: シンボルに関連付けられたメモリにデータをバインドします。例:DATA runtime·isplan9(SB)/4, $1
。シンボル名に続いてスラッシュとバイト数を記述します。GLOBL
: シンボルをグローバルとして宣言します。引数としてオプションのフラグと宣言されるデータのサイズを取ります。初期値はすべてゼロですが、DATA
ディレクティブで初期化できます。GLOBL
ディレクティブは、対応するDATA
ディレクティブの後に記述する必要があります。- ディレクティブフラグ:
src/cmd/ld/textflag.h
で定義されており、数値式またはシンボル名で指定できます。NOPROF
(1):TEXT
項目用。プロファイリングを行わない(非推奨)。DUPOK
(2): 複数のインスタンスがバイナリに存在しても合法。リンカが1つを選択。NOSPLIT
(4):TEXT
項目用。スタック分割チェックのプリアンブルを挿入しない。スタック分割コード自体のようなルーチンを保護するために使用されます。RODATA
(8):DATA
およびGLOBL
項目用。読み取り専用セクションにデータを配置。NOPTR
(16):DATA
およびGLOBL
項目用。ポインタを含まず、ガベージコレクタによるスキャンが不要。WRAPPER
(32):TEXT
項目用。ラッパー関数であり、recover
を無効にするものとしてカウントされない。
-
アーキテクチャ固有の詳細:
- 命令セットの定義: 各アーキテクチャで定義されている命令は、対応するリンカのトップレベルヘッダーファイル(例:
8l
の場合は$GOROOT/src/cmd/8l/8.out.h
)のC列挙型as
で確認できます。各命令はA
で始まります(例:AADCB
はADCB
命令を表す)。 - データフロー: 命令内のデータは左から右に流れます(例:
MOVQ $0, CX
はCX
をクリアする)。これは、通常のデータフローが逆方向のアーキテクチャでも適用されます。 - 32-bit Intel 386:
m
とg
構造体へのランタイムポインタは、MMU内の未使用レジスタ(Goの観点からは)の値を通じて維持されます。get_tls
マクロがアセンブラ用に定義されており、g
とm
ポインタをロードするために使用されます。 - 64-bit Intel 386 (amd64):
m
とg
ポインタへのアクセスコードは386と同じですが、MOVL
の代わりにMOVQ
を使用します。 - ARM:
R9
とR10
レジスタは、それぞれm
(マシン)とg
(ゴルーチン)構造体を指すためにコンパイラとリンカによって予約されています。アセンブラソースコード内では、これらは単にm
とg
として参照できます。TEXT
を定義する際にフレームサイズを$-4
と指定すると、リンカに対して、これがLR
をエントリで保存する必要のないリーフ関数であることを伝えます。
- 命令セットの定義: 各アーキテクチャで定義されている命令は、対応するリンカのトップレベルヘッダーファイル(例:
-
サポートされていないオペコード:
- アセンブラはコンパイラをサポートするように設計されているため、すべてのハードウェア命令がすべてのアーキテクチャで定義されているわけではありません。
- 不足している命令を使用する必要がある場合、アセンブラを更新するか、
BYTE
およびWORD
ディレクティブを使用して、命令ストリーム内に明示的なデータを配置することができます。例として、386ランタイムでの64ビットアトミックロード関数の実装が示されています。
コアとなるコードの変更箇所
このコミットは、doc/asm.html
という新しいファイルをdoc/
ディレクトリに追加します。このファイルは、Goのアセンブラに関する包括的なガイドのHTMLドキュメントです。
--- /dev/null
+++ doc/asm.html
@@ -0,0 +1,402 @@
+<!--{
+ "Title": "A Quick Guide to Go's Assembler",
+ "Path": "/doc/asm.html"
+}-->
+
+<h2 id="introduction">A Quick Guide to Go's Assembler</h2>
+
+<p>
+This document is a quick outline of the unusual form of assembly language used by the <code>gc</code>
+suite of Go compilers (<code>6g</code>, <code>8g</code>, etc.).
+It is based on the input to the Plan 9 assemblers, which is documented in detail
+<a href="http://plan9.bell-labs.com/sys/doc/asm.html">on the Plan 9 site</a>.
+If you plan to write assembly language, you should read that document although much of it is Plan 9-specific.
+This document provides a summary of the syntax and
+describes the peculiarities that apply when writing assembly code to interact with Go.
+</p>
+
+... (以下、上記「技術的詳細」で説明した内容のHTMLマークアップが続く)
コアとなるコードの解説
追加されたdoc/asm.html
は、Goのアセンブラの入門ガイドとして機能します。このドキュメントは、GoのアセンブラがPlan 9のアセンブラに由来すること、そしてGoのコンパイラとリンカの特殊な連携により、アセンブリコードが直接的な機械語の表現ではないという重要な概念から始まります。
ドキュメントは、Goのアセンブラにおけるシンボルの扱い(SB
, FP
擬似レジスタ、パッケージパスとシンボル名の表記法)、TEXT
, DATA
, GLOBL
といった主要なディレクティブとそのフラグ(NOSPLIT
, RODATA
, NOPTR
など)について詳しく説明しています。
さらに、32-bit Intel 386、64-bit Intel 386 (amd64)、ARMといったサポートされるアーキテクチャごとの具体的な詳細(m
とg
ポインタのアクセス方法、get_tls
マクロ、レジスタの予約など)も提供しています。
最後に、アセンブラがすべてのハードウェア命令をサポートしているわけではないこと、そして不足している命令をBYTE
やWORD
ディレクティブを使って直接埋め込む方法についても触れています。
このドキュメントは、Goのアセンブリコードを読み書きする開発者にとって不可欠なリソースとなり、Goの低レベルな動作を理解するための基礎を提供します。
関連リンク
- Go issue #6060: https://github.com/golang/go/issues/6060
- Plan 9 Assembler Documentation: http://plan9.bell-labs.com/sys/doc/asm.html
- Plan 9 Compiler Documentation: http://plan9.bell-labs.com/sys/doc/compiler.html
- Go runtime package: https://pkg.go.dev/runtime
- Go math/big package: https://pkg.go.dev/math/big
src/cmd/ld/textflag.h
(Go source code): https://github.com/golang/go/blob/master/src/cmd/ld/textflag.h
参考にした情報源リンク
- コミットメッセージと変更されたファイルの内容
- Go言語の公式ドキュメント(
doc/asm.html
の内容自体) - Plan 9の関連ドキュメント(
doc/asm.html
内で参照されているもの) - Go言語のソースコード(
src/cmd/ld/textflag.h
など) - Go言語のアセンブリに関する一般的な知識とコミュニティの議論