[インデックス 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言語のアセンブリに関する一般的な知識とコミュニティの議論