Goのアセンブラクイックガイド
Goのアセンブラクイックガイド
このドキュメントは、gc Goコンパイラで使用されるアセンブリ言語の特殊な形式の概要を簡単に説明したものです。このドキュメントは網羅的なものではありません。
アセンブラはPlan 9アセンブラの入力スタイルに基づいており、その詳細は別の場所に記載されています。アセンブリ言語を書く予定がある場合は、そのドキュメントを読むべきですが、その大部分はPlan 9固有のものです。このドキュメントは、そのドキュメントで説明されている構文と違いの要約を提供し、Goと対話するアセンブリコードを書く際に適用される特殊性について説明します。
Goのアセンブラについて知っておくべき最も重要なことは、それが基盤となるマシンを直接表現したものではないということです。一部の詳細はマシンに正確に対応していますが、そうでないものもあります。これは、コンパイラスイート(この説明を参照)が通常のパイプラインでアセンブラパスを必要としないためです。代わりに、コンパイラは一種の半抽象的な命令セットで動作し、命令選択はコード生成後に行われます。アセンブラは半抽象的な形式で動作するため、MOVのような命令を見たときにツールチェーンが実際にその操作に対して生成するものは、移動命令ではなく、クリアまたはロードである可能性があります。あるいは、その名前の機械命令に正確に対応している場合もあります。一般的に、マシン固有の操作はそれ自体として現れる傾向がありますが、メモリ移動やサブルーチン呼び出し、リターンなどのより一般的な概念はより抽象的です。詳細はアーキテクチャによって異なり、不正確さをお詫び申し上げます。状況は明確に定義されていません。
アセンブラプログラムは、その半抽象的な命令セットの記述を解析し、リンカへの入力となる命令に変換する方法です。特定のアーキテクチャ、例えばamd64のアセンブリで命令がどのように見えるかを知りたい場合は、標準ライブラリのソース、例えばruntimeやmath/bigのようなパッケージに多くの例があります。コンパイラがアセンブリコードとして出力するもの(実際の出力はここで見るものと異なる場合があります)を調べることもできます。
$ cat x.go
package main
func main() {
println(3)
}
$ GOOS=linux GOARCH=amd64 go tool compile -S x.go # or: go build -gcflags -S x.go
"".main STEXT size=74 args=0x0 locals=0x10
0x0000 00000 (x.go:3) TEXT "".main(SB), $16-0
0x0000 00000 (x.go:3) MOVQ (TLS), CX
0x0009 00009 (x.go:3) CMPQ SP, 16(CX)
0x000d 00013 (x.go:3) JLS 67
0x000f 00015 (x.go:3) SUBQ $16, SP
0x0013 00019 (x.go:3) MOVQ BP, 8(SP)
0x0018 00024 (x.go:3) LEAQ 8(SP), BP
0x001d 00029 (x.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (x.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (x.go:3) FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (x.go:4) PCDATA $0, $0
0x001d 00029 (x.go:4) PCDATA $1, $0
0x001d 00029 (x.go:4) CALL runtime.printlock(SB)
0x0022 00034 (x.go:4) MOVQ $3, (SP)
0x002a 00042 (x.go:4) CALL runtime.printint(SB)
0x002f 00047 (x.go:4) CALL runtime.printnl(SB)
0x0034 00052 (x.go:4) CALL runtime.printunlock(SB)
0x0039 00057 (x.go:5) MOVQ 8(SP), BP
0x003e 00062 (x.go:5) ADDQ $16, SP
0x0042 00066 (x.go:5) RET
0x0043 00067 (x.go:5) NOP
0x0043 00067 (x.go:3) PCDATA $1, $-1
0x0043 00067 (x.go:3) PCDATA $0, $-1
0x0043 00067 (x.go:3) CALL runtime.morestack_noctxt(SB)
0x0048 00072 (x.go:3) JMP 0
...
FUNCDATAおよびPCDATAディレクティブには、ガベージコレクタが使用する情報が含まれています。これらはコンパイラによって導入されます。
リンク後にバイナリに何が格納されるかを確認するには、go tool objdumpを使用します。
$ go build -o x.exe x.go $ go tool objdump -s main.main x.exe TEXT main.main(SB) /tmp/x.go x.go:3 0x10501c0 65488b0c2530000000 MOVQ GS:0x30, CX x.go:3 0x10501c9 483b6110 CMPQ 0x10(CX), SP x.go:3 0x10501cd 7634 JBE 0x1050203 x.go:3 0x10501cf 4883ec10 SUBQ $0x10, SP x.go:3 0x10501d3 48896c2408 MOVQ BP, 0x8(SP) x.go:3 0x10501d8 488d6c2408 LEAQ 0x8(SP), BP x.go:4 0x10501dd e86e45fdff CALL runtime.printlock(SB) x.go:4 0x10501e2 48c7042403000000 MOVQ $0x3, 0(SP) x.go:4 0x10501ea e8e14cfdff CALL runtime.printint(SB) x.go:4 0x10501ef e8ec47fdff CALL runtime.printnl(SB) x.go:4 0x10501f4 e8d745fdff CALL runtime.printunlock(SB) x.go:5 0x10501f9 488b6c2408 MOVQ 0x8(SP), BP x.go:5 0x10501fe 4883c410 ADDQ $0x10, SP x.go:5 0x1050202 c3 RET x.go:3 0x1050203 e83882ffff CALL runtime.morestack_noctxt(SB) x.go:3 0x1050208 ebb6 JMP main.main(SB)
定数
アセンブラはPlan 9アセンブラから着想を得ていますが、別のプログラムであるため、いくつかの違いがあります。1つは定数評価です。アセンブラの定数式は、元のCライクな優先順位ではなく、Goの演算子の優先順位を使用して解析されます。したがって、3&1<<2は0ではなく4です。これは(3&1)<<2として解析され、3&(1<<2)ではありません。また、定数は常に64ビットの符号なし整数として評価されます。したがって、-2は整数値のマイナス2ではなく、同じビットパターンを持つ符号なし64ビット整数です。この違いが問題になることはほとんどありませんが、曖昧さを避けるために、右オペランドの最上位ビットがセットされている除算または右シフトは拒否されます。
シンボル
R1やLRのような一部のシンボルは事前定義されており、レジスタを参照します。正確なセットはアーキテクチャによって異なります。
擬似レジスタを参照する4つの事前宣言されたシンボルがあります。これらは実際のレジスタではなく、フレームポインタなどのツールチェーンによって維持される仮想レジスタです。擬似レジスタのセットはすべてのアーキテクチャで同じです。
-
FP: フレームポインタ: 引数とローカル変数。 -
PC: プログラムカウンタ: ジャンプと分岐。 -
SB: 静的ベースポインタ: グローバルシンボル。 -
SP: スタックポインタ: ローカルスタックフレーム内の最高アドレス。
すべてのユーザー定義シンボルは、擬似レジスタFP(引数とローカル変数)およびSB(グローバル変数)へのオフセットとして記述されます。
SB擬似レジスタはメモリの起点と考えることができ、シンボルfoo(SB)はメモリ内のアドレスとしての名前fooです。この形式は、グローバル関数およびデータを命名するために使用されます。名前の後に<>を追加すると、例えばfoo<>(SB)のように、Cファイルのトップレベルのstatic宣言のように、名前は現在のソースファイル内でのみ表示されます。名前にオフセットを追加すると、シンボルのアドレスからのそのオフセットを参照するため、foo+4(SB)はfooの開始から4バイト先を指します。
FP擬似レジスタは、関数引数を参照するために使用される仮想フレームポインタです。コンパイラは仮想フレームポインタを維持し、スタック上の引数をその擬似レジスタからのオフセットとして参照します。したがって、0(FP)は関数の最初の引数であり、8(FP)は2番目(64ビットマシン上)といった具合です。ただし、このように関数引数を参照する場合、first_arg+0(FP)やsecond_arg+8(FP)のように、先頭に名前を付ける必要があります。(オフセットの意味—フレームポインタからのオフセット—は、SBで使用する場合とは異なり、シンボルからのオフセットです。)アセンブラはこの慣例を強制し、単なる0(FP)や8(FP)は拒否します。実際の名前は意味的に無関係ですが、引数の名前を文書化するために使用する必要があります。FPは、ハードウェアフレームポインタを持つアーキテクチャであっても、常に擬似レジスタであり、ハードウェアレジスタではないことを強調する価値があります。
Goプロトタイプを持つアセンブリ関数では、go vetが引数名とオフセットが一致するかどうかをチェックします。32ビットシステムでは、64ビット値の低位32ビットと高位32ビットは、arg_lo+0(FP)またはarg_hi+4(FP)のように、名前に_loまたは_hiサフィックスを追加することで区別されます。Goプロトタイプが結果に名前を付けていない場合、期待されるアセンブリ名はretです。
SP擬似レジスタは、フレームローカル変数と関数呼び出しのために準備されている引数を参照するために使用される仮想スタックポインタです。ローカルスタックフレーム内の最高アドレスを指すため、参照は[−framesize, 0)の範囲の負のオフセットを使用する必要があります。例えば、x-8(SP)、y-4(SP)などです。
SPという名前のハードウェアレジスタを持つアーキテクチャでは、名前のプレフィックスによって、仮想スタックポインタへの参照とアーキテクチャSPレジスタへの参照が区別されます。つまり、x-8(SP)と-8(SP)は異なるメモリアドレスです。前者は仮想スタックポインタ擬似レジスタを参照し、後者はハードウェアのSPレジスタを参照します。
SPとPCが物理的な番号付きレジスタのエイリアスである機械では、GoアセンブラではSPとPCという名前は引き続き特別に扱われます。例えば、SPへの参照にはFPと同じようにシンボルが必要です。実際のハードウェアレジスタにアクセスするには、真のR名を使用します。例えば、ARMアーキテクチャでは、ハードウェアのSPとPCはR13とR15としてアクセスできます。
分岐と直接ジャンプは常にPCへのオフセットとして、またはラベルへのジャンプとして記述されます。
label: MOVW $0, R1 JMP label
各ラベルは、それが定義されている関数内でのみ可視です。したがって、1つのファイル内の複数の関数が同じラベル名を定義および使用することは許可されています。直接ジャンプおよび呼び出し命令は、name(SB)のようなテキストシンボルをターゲットにできますが、name+4(SB)のようなシンボルからのオフセットはターゲットにできません。
命令、レジスタ、およびアセンブラディレクティブは常に大文字で記述され、アセンブリプログラミングが困難な作業であることを思い出させます。(例外: ARMのgレジスタの名前変更。)
Goオブジェクトファイルおよびバイナリでは、シンボルの完全な名前はパッケージパスにピリオドとシンボル名が続いたものです。例えば、fmt.Printfやmath/rand.Intです。アセンブラのパーサーはピリオドとスラッシュを句読点として扱うため、これらの文字列は直接識別子名として使用できません。代わりに、アセンブラは識別子内で中点文字U+00B7と除算スラッシュU+2215を許可し、それらを通常のピリオドとスラッシュに書き換えます。アセンブラソースファイル内では、上記のシンボルはfmt·Printfおよびmath∕rand·Intと記述されます。コンパイラが-Sフラグを使用して生成するアセンブリリストでは、アセンブラによって要求されるUnicode置換ではなく、ピリオドとスラッシュが直接表示されます。
ほとんどの手書きアセンブリファイルでは、シンボル名に完全なパッケージパスは含まれません。リンカは、ピリオドで始まるすべての名前の先頭に現在のオブジェクトファイルのパッケージパスを挿入するためです。math/randパッケージ実装内のアセンブリソースファイルでは、パッケージのInt関数は·Intとして参照できます。この慣例により、パッケージのインポートパスを自身のソースコードにハードコーディングする必要がなくなり、コードをある場所から別の場所に移動するのが容易になります。
ディレクティブ
アセンブラは、テキストとデータをシンボル名に結合するためにさまざまなディレクティブを使用します。たとえば、ここに単純で完全な関数定義があります。TEXTディレクティブはシンボルruntime·profileloopを宣言し、それに続く命令が関数の本体を構成します。TEXTブロックの最後の命令は、通常はRET(擬似)命令のような何らかのジャンプでなければなりません。(そうでない場合、リンカは自己ジャンプ命令を追加します。TEXTsにはフォールススルーはありません。)シンボルの後には、フラグ(下記参照)とフレームサイズ、定数(下記参照)が続きます。
TEXT runtime·profileloop(SB),NOSPLIT,$8 MOVQ $runtime·profileloop1(SB), CX MOVQ CX, 0(SP) CALL runtime·externalthreadhandler(SB) RET
一般的な場合、フレームサイズの後に引数サイズがマイナス記号で区切られて続きます。(これは減算ではなく、単に独特の構文です。)フレームサイズ$24-8は、関数が24バイトのフレームを持ち、呼び出し元のフレーム上に存在する8バイトの引数で呼び出されることを示します。TEXTにNOSPLITが指定されていない場合、引数サイズを指定する必要があります。Goプロトタイプを持つアセンブリ関数では、go vetが引数サイズが正しいことをチェックします。
シンボル名がコンポーネントを区切るために中点を使用し、静的ベース擬似レジスタSBからのオフセットとして指定されていることに注意してください。この関数は、パッケージruntimeのGoソースから、単純な名前profileloopを使用して呼び出されます。
グローバルデータシンボルは、初期化するDATAディレクティブのシーケンスと、それに続くGLOBLディレクティブによって定義されます。各DATAディレクティブは、対応するメモリのセクションを初期化します。明示的に初期化されていないメモリはゼロ化されます。DATAディレクティブの一般形式は次のとおりです。
DATA symbol+offset(SB)/width, value
これにより、指定されたオフセットと幅でシンボルメモリが指定された値で初期化されます。特定のシンボルに対するDATAディレクティブは、オフセットを増やす順序で記述する必要があります。
GLOBLディレクティブは、シンボルをグローバルとして宣言します。引数はオプションのフラグと、グローバルとして宣言されるデータのサイズです。DATAディレクティブが初期化しない限り、初期値はすべてゼロになります。GLOBLディレクティブは、対応するすべてのDATAディレクティブの後に記述する必要があります。
例えば、
DATA divtab<>+0x00(SB)/4, $0xf4f8fcff DATA divtab<>+0x04(SB)/4, $0xe6eaedf0 ... DATA divtab<>+0x3c(SB)/4, $0x81828384 GLOBL divtab<>(SB), RODATA, $64 GLOBL runtime·tlsoffset(SB), NOPTR, $4
は、読み取り専用の64バイトの4バイト整数値のテーブルであるdivtab<>を宣言し、初期化し、ポインタを含まない4バイトの暗黙的にゼロ化された変数であるruntime·tlsoffsetを宣言します。
ディレクティブには1つまたは2つの引数がある場合があります。2つある場合、最初の引数はフラグのビットマスクであり、数値式として記述したり、加算または論理和で結合したり、人間が理解しやすいようにシンボルで設定したりできます。それらの値は、標準の#includeファイルtextflag.hで定義されており、次のとおりです。
-
NOPROF= 1
(TEXT項目用。)マークされた関数をプロファイリングしない。このフラグは非推奨です。 -
DUPOK= 2
単一のバイナリにこのシンボルの複数のインスタンスが存在することは合法です。リンカは、使用する重複のいずれかを選択します。 -
NOSPLIT= 4
(TEXT項目用。)スタックを分割する必要があるかどうかをチェックするプリアンブルを挿入しない。ルーチンのフレームと、それが呼び出すすべてのものは、現在のスタックセグメントに残っている空き領域に収まる必要があります。スタック分割コード自体のようなルーチンを保護するために使用されます。 -
RODATA= 8
(DATAおよびGLOBL項目用。)このデータを読み取り専用セクションに配置します。 -
NOPTR= 16
(DATAおよびGLOBL項目用。)このデータにはポインタが含まれていないため、ガベージコレクタによってスキャンする必要はありません。 -
WRAPPER= 32
(TEXT項目用。)これはラッパー関数であり、recoverを無効にするものとしてカウントすべきではありません。 -
NEEDCTXT= 64
(TEXT項目用。)この関数はクロージャであるため、着信コンテキストレジスタを使用します。 -
LOCAL= 128
このシンボルは動的共有オブジェクトに対してローカルです。 -
TLSBSS= 256
(DATAおよびGLOBL項目用。)このデータをスレッドローカルストレージに配置します。 -
NOFRAME= 512
(TEXT項目用。)これがリーフ関数でない場合でも、スタックフレームを割り当ててリターンアドレスを保存/復元する命令を挿入しない。フレームサイズが0と宣言された関数でのみ有効。 -
TOPFRAME= 2048
(TEXT項目用。)関数はコールスタックの最も外側のフレームです。トレースバックはこの関数で停止する必要があります。
特殊命令
PCALIGN擬似命令は、次の命令を指定された境界にノーオペレーション命令でパディングして整列させることを示すために使用されます。
現在、arm64、amd64、ppc64、loong64、riscv64でサポートされています。例えば、以下のMOVD命令の開始は32バイトに整列されます。
PCALIGN $32 MOVD $2, R0
Goの型と定数との相互作用
パッケージに.sファイルがある場合、go buildはコンパイラにgo_asm.hという特殊なヘッダーを出力するよう指示し、.sファイルはそれを#includeできます。このファイルには、Go構造体フィールドのオフセット、Go構造体型のサイズ、および現在のパッケージで定義されているほとんどのGo const宣言のシンボリックな#define定数が含まれています。Goのアセンブリは、Goの型のレイアウトに関する仮定を避けるべきであり、代わりにこれらの定数を使用すべきです。これにより、アセンブリコードの可読性が向上し、Goの型定義またはGoコンパイラが使用するレイアウトルールにおけるデータレイアウトの変更に対して堅牢性が保たれます。
定数はconst_nameの形式です。例えば、Goの宣言const bufSize = 1024が与えられた場合、アセンブリコードはこの定数の値をconst_bufSizeとして参照できます。
フィールドオフセットはtype_fieldの形式です。構造体のサイズはtype__sizeの形式です。例えば、以下のGoの定義を考えてみましょう。
type reader struct {
buf [bufSize]byte
r int
}
アセンブリは、この構造体のサイズをreader__sizeとして、2つのフィールドのオフセットをreader_bufとreader_rとして参照できます。したがって、レジスタR1がreaderへのポインタを含んでいる場合、アセンブリはrフィールドをreader_r(R1)として参照できます。
これらの#define名が曖昧な場合(例えば、_sizeフィールドを持つ構造体の場合)、#include "go_asm.h"は「redefinition of macro」エラーで失敗します。
ランタイム連携
ガベージコレクションが正しく実行されるためには、ランタイムはすべてのグローバルデータとほとんどのスタックフレーム内のポインタの位置を知っている必要があります。GoコンパイラはGoソースファイルをコンパイルするときにこの情報を出力しますが、アセンブリプログラムはそれを明示的に定義する必要があります。
NOPTRフラグ(上記参照)でマークされたデータシンボルは、ランタイムによって割り当てられたデータへのポインタを含まないものとして扱われます。RODATAフラグを持つデータシンボルは読み取り専用メモリに割り当てられ、したがって暗黙的にNOPTRとマークされたものとして扱われます。ポインタより小さい合計サイズを持つデータシンボルも、暗黙的にNOPTRとマークされたものとして扱われます。アセンブリソースファイルでポインタを含むシンボルを定義することはできません。そのようなシンボルは代わりにGoソースファイルで定義する必要があります。アセンブリソースは、DATAおよびGLOBLディレクティブがなくても、名前でシンボルを参照できます。一般的な経験則として、非RODATAシンボルはすべてアセンブリではなくGoで定義することをお勧めします。
各関数は、その引数、結果、およびローカルスタックフレーム内のライブポインタの位置を示すアノテーションも必要です。ポインタの結果がなく、ローカルスタックフレームもない、または関数呼び出しもないアセンブリ関数については、同じパッケージ内のGoソースファイルに関数のGoプロトタイプを定義するだけで済みます。アセンブリ関数の名前にはパッケージ名コンポーネントを含めるべきではありません(例えば、パッケージsyscallの関数Syscallは、TEXTディレクティブで同等の名前syscall·Syscallではなく、名前·Syscallを使用すべきです)。より複雑な状況では、明示的なアノテーションが必要です。これらのアノテーションは、標準の#includeファイルfuncdata.hで定義されている擬似命令を使用します。
関数に引数と結果がない場合、ポインタ情報は省略できます。これはTEXT命令の引数サイズアノテーション$n-0で示されます。それ以外の場合、Goから直接呼び出されないアセンブリ関数であっても、関数に対するGoソースファイル内のGoプロトタイプによってポインタ情報を提供する必要があります。(プロトタイプは、go vetが引数参照をチェックすることも可能にします。)関数の開始時に、引数は初期化されていると仮定されますが、結果は未初期化であると仮定されます。結果が呼び出し命令中にライブポインタを保持する場合、関数は結果をゼロクリアし、擬似命令GO_RESULTS_INITIALIZEDを実行して開始する必要があります。この命令は、結果が初期化され、スタック移動およびガベージコレクション中にスキャンされるべきであることを記録します。アセンブリ関数がポインタを返さないか、または呼び出し命令を含まないように手配する方が通常は簡単です。標準ライブラリのどのAアセンブリ関数もGO_RESULTS_INITIALIZEDを使用していません。
関数にローカルスタックフレームがない場合、ポインタ情報は省略できます。これは、TEXT命令のローカルフレームサイズアノテーション$0-nで示されます。関数に呼び出し命令が含まれていない場合も、ポインタ情報は省略できます。それ以外の場合、ローカルスタックフレームにポインタを含めるべきではなく、アセンブリは擬似命令NO_LOCAL_POINTERSを実行することでこの事実を確認する必要があります。スタックのサイズ変更はスタックの移動によって実装されるため、スタックポインタは任意の関数呼び出し中に変更される可能性があります。スタックデータへのポインタでさえ、ローカル変数に保持すべきではありません。
アセンブリ関数には常にGoプロトタイプを与えるべきです。引数と結果のポインタ情報を提供するためと、go vetがそれらにアクセスするために使用されるオフセットが正しいことをチェックできるようにするためです。
アーキテクチャ固有の詳細
各マシンのすべての命令やその他の詳細をリストアップすることは現実的ではありません。特定のマシン、例えばARMについてどのような命令が定義されているかを見るには、そのアーキテクチャのobjサポートライブラリのソースを、ディレクトリsrc/cmd/internal/obj/armで探してください。そのディレクトリにはa.out.goというファイルがあり、次のようなAで始まる定数の長いリストが含まれています。
const ( AAND = obj.ABaseARM + obj.A_ARCHSPECIFIC + iota AEOR ASUB ARSB AADD ...
これは、そのアーキテクチャのアセンブラとリンカに認識されている命令とそのスペルのリストです。このリストの各命令は先頭に大文字のAが付いているので、AANDはビットごとのAND命令(先頭のAなしのAND)を表し、アセンブリソースではANDと記述されます。列挙はほとんどアルファベット順です。(cmd/internal/objパッケージで定義されているアーキテクチャに依存しないAXXXは、無効な命令を表します。)Aの名前のシーケンスは、実際の機械命令のエンコーディングとは関係ありません。cmd/internal/objパッケージがその詳細を処理します。
386およびAMD64アーキテクチャの両方の命令は、cmd/internal/obj/x86/a.out.goにリストされています。
アーキテクチャは、(R1)(レジスタ間接)、4(R1)(オフセット付きレジスタ間接)、$foo(SB)(絶対アドレス)などの一般的なアドレッシングモードの構文を共有しています。アセンブラは、各アーキテクチャに固有のいくつかのアドレッシングモード(必ずしもすべてではありません)もサポートしています。以下のセクションにこれらをリストします。
前のセクションの例から明らかな詳細の1つは、命令内のデータが左から右に流れることです。MOVQ $0, CXはCXをクリアします。このルールは、従来の表記法が逆方向を使用するアーキテクチャでも適用されます。
以下に、サポートされているアーキテクチャに関する主要なGo固有の詳細のいくつかについて説明します。
32ビット Intel 386
g構造体へのランタイムポインタは、MMU内の(Goに関する限り)他に未使用のレジスタの値を通じて維持されます。ランタイムパッケージでは、アセンブリコードはgo_tls.hを含めることができます。このファイルは、このレジスタにアクセスするためのOSおよびアーキテクチャ依存のマクロget_tlsを定義しています。get_tlsマクロは1つの引数を取ります。これはgポインタをロードするレジスタです。
例えば、CXを使用してgとmをロードするシーケンスは次のようになります。
#include "go_tls.h" #include "go_asm.h" ... get_tls(CX) MOVL g(CX), AX // Move g into AX. MOVL g_m(AX), BX // Move g.m into BX.
get_tlsマクロはamd64でも定義されています。
アドレッシングモード
-
(DI)(BX*2): アドレスDIにBX*2を加えた場所。 -
64(DI)(BX*2): アドレスDIにBX*2と64を加えた場所。これらのモードは、スケールファクタとして1, 2, 4, 8のみを受け入れます。
コンパイラとアセンブラの-dynlinkまたは-sharedモードを使用する場合、グローバル変数などの固定メモリ位置へのロードまたはストアは、CXを上書きすると見なされる必要があります。したがって、これらのモードで安全に使用するために、アセンブリソースは通常、メモリ参照間を除きCXを避けるべきです。
64ビットIntel 386 (別名amd64)
この2つのアーキテクチャは、アセンブラレベルではほぼ同じように動作します。64ビット版でmとgポインタにアクセスするためのアセンブリコードは、32ビット386と同じですが、MOVLではなくMOVQを使用します。
get_tls(CX) MOVQ g(CX), AX // Move g into AX. MOVQ g_m(AX), BX // Move g.m into BX.
レジスタBPはカリ―セーブです。フレームサイズがゼロより大きい場合、アセンブラは自動的にBPの保存/復元を挿入します。BPを汎用レジスタとして使用することは許可されていますが、サンプリングベースのプロファイリングと競合する可能性があります。
ARM
レジスタR10とR11はコンパイラとリンカによって予約されています。
R10はg (ゴルーチン) 構造体を指します。アセンブラソースコード内では、このポインタはgとして参照されなければなりません。R10という名前は認識されません。
人間とコンパイラがアセンブリを書きやすくするために、ARMリンカは一般的なアドレッシング形式と、単一のハードウェア命令では表現できないDIVやMODのような擬似命令を許可します。これらの形式は複数の命令として実装され、しばしばR11レジスタを一時値を保持するために使用します。手書きのアセンブリはR11を使用できますが、その場合、リンカが関数の他の命令を実装するためにR11を使用していないことを確認する必要があります。
TEXTを定義する際、フレームサイズ$-4を指定すると、リンカに、これがエントリー時にLRを保存する必要のないリーフ関数であることを伝えます。
SPという名前は、常に以前に説明した仮想スタックポインタを参照します。ハードウェアレジスタにはR13を使用します。
条件コードの構文は、命令の後にピリオドと1文字または2文字のコードを追加するもので、MOVW.EQのようになります。複数のコードを追加できます。MOVM.IA.W。コード修飾子の順序は関係ありません。
アドレッシングモード
-
R0->16
R0>>16
R0<<16
R0@>16:<<の場合、R0を16ビット左シフトします。他のコードは、->(算術右シフト)、>>(論理右シフト)、@>(右回転)です。 -
R0->R1
R0>>R1
R0<
R0@>R1:<<の場合、R0をR1のカウントで左シフトします。他のコードは、->(算術右シフト)、>>(論理右シフト)、@>(右回転)です。 -
[R0,g,R12-R15]: マルチレジスタ命令の場合、R0、g、およびR12からR15(両端を含む)で構成されるセット。 -
(R5, R6): 宛先レジスタペア。
ARM64
R18は「プラットフォームレジスタ」であり、Appleプラットフォームで予約されています。誤って誤用されるのを防ぐため、レジスタはR18_PLATFORMと命名されています。R27とR28はコンパイラとリンカによって予約されています。R29はフレームポインタです。R30はリンクレジスタです。
命令修飾子は、命令にピリオドを付けて追加されます。唯一の修飾子はP(ポストインクリメント)とW(プレインクリメント)です。MOVW.P, MOVW.W
アドレッシングモード
-
R0->16
R0>>16
R0<<16
R0@>16: これらは32ビットARMと同じです。 -
$(8<<12): 即値8を12ビット左シフトします。 -
8(R0):R0の値と8を加算します。 -
(R2)(R0):R0とR2の合計のアドレス。 -
R0.UXTB
R0.UXTB<<imm:UXTB:R0の最下位ビットから8ビット値を抽出し、R0のサイズにゼロ拡張します。R0.UXTB<<imm:R0.UXTBの結果をimmビットだけ左シフトします。imm値は0、1、2、3、4のいずれかです。他の拡張にはUXTH(16ビット)、UXTW(32ビット)、UXTX(64ビット)があります。 -
R0.SXTB
R0.SXTB<<imm:SXTB:R0の最下位ビットから8ビット値を抽出し、R0のサイズに符号拡張します。R0.SXTB<<imm:R0.SXTBの結果をimmビットだけ左シフトします。imm値は0、1、2、3、4のいずれかです。他の拡張にはSXTH(16ビット)、SXTW(32ビット)、SXTX(64ビット)があります。 -
(R5, R6):LDAXP/LDP/LDXP/STLXP/STP/STP用のレジスタペア。
参照: Go ARM64アセンブリ命令リファレンスマニュアル
PPC64
このアセンブラは、GOARCH値ppc64およびppc64leによって使用されます。
参照: Go PPC64アセンブリ命令リファレンスマニュアル
IBM z/アーキテクチャ、別名s390x
レジスタR10およびR11は予約されています。アセンブラは、一部の命令をアセンブルするときに、一時値を保持するためにこれらを使用します。
R13はg (ゴルーチン) 構造体を指します。このレジスタはgとして参照されなければなりません。R13という名前は認識されません。
R15はスタックフレームを指し、通常は仮想レジスタSPおよびFPを使用してのみアクセスされるべきです。
ロード・ストア多重命令は、レジスタの範囲を操作します。レジスタの範囲は、開始レジスタと終了レジスタで指定されます。たとえば、LMG (R9), R5, R7は、0(R9)、8(R9)、16(R9)のアドレスにある64ビット値をそれぞれR5、R6、R7にロードします。
MVCやXCなどのストレージ・ストレージ命令は、長さが最初の引数として記述されます。例えば、XC $8, (R9), (R9)は、R9で指定されたアドレスの8バイトをクリアします。
ベクトル命令が長さまたはインデックスを引数として取る場合、それは最初の引数になります。例えば、VLEIF $1, $16, V2は、値16をV2のインデックス1にロードします。ベクトル命令を使用する際は、実行時にそれらが利用可能であることを確認するために注意を払う必要があります。ベクトル命令を使用するには、マシンにベクトル機能(機能リストのビット129)とカーネルサポートの両方が必要です。カーネルサポートなしでは、ベクトル命令は効果がなく(NOP命令と同等になります)。
アドレッシングモード
-
(R5)(R6*1):R5にR6を加えた場所。x86と同様にスケールモードですが、許可されるスケールは1のみです。
MIPS, MIPS64
汎用レジスタはR0からR31、浮動小数点レジスタはF0からF31と命名されます。
R30はgを指すために予約されています。R23は一時レジスタとして使用されます。
TEXTディレクティブで、MIPSの場合はフレームサイズ$-4、MIPS64の場合は$-8を指定すると、リンカはLRを保存しないように指示されます。
SPは仮想スタックポインタを参照します。ハードウェアレジスタにはR29を使用します。
アドレッシングモード
-
16(R1):R1に16を加えた場所。 -
(R1):0(R1)のエイリアス。
GOMIPS環境変数(hardfloatまたはsoftfloat)の値は、GOMIPS_hardfloatまたはGOMIPS_softfloatのいずれかを事前定義することによってアセンブリコードで利用できるようになります。
GOMIPS64環境変数(hardfloatまたはsoftfloat)の値は、GOMIPS64_hardfloatまたはGOMIPS64_softfloatのいずれかを事前定義することによってアセンブリコードで利用できるようになります。
未サポートのオペコード
アセンブラはコンパイラをサポートするように設計されているため、すべてのハードウェア命令がすべてのアーキテクチャで定義されているわけではありません。コンパイラが生成しない場合、そこには存在しない可能性があります。不足している命令を使用する必要がある場合、2つの方法があります。1つは、その命令をサポートするようにアセンブラを更新することです。これは簡単ですが、その命令が再び使用される可能性が高い場合にのみ価値があります。代わりに、単純な一度限りのケースでは、BYTEおよびWORDディレクティブを使用して、TEXT内の命令ストリームに明示的なデータを配置することが可能です。以下は、386ランタイムが64ビットアトミックロード関数を定義する方法です。
// uint64 atomicload64(uint64 volatile* addr); // so actually // void atomicload64(uint64 *res, uint64 volatile *addr); TEXT runtime·atomicload64(SB), NOSPLIT, $0-12 MOVL ptr+0(FP), AX TESTL $7, AX JZ 2(PC) MOVL 0, AX // crash with nil ptr deref LEAL ret_lo+4(FP), BX // MOVQ (%EAX), %MM0 BYTE $0x0f; BYTE $0x6f; BYTE $0x00 // MOVQ %MM0, 0(%EBX) BYTE $0x0f; BYTE $0x7f; BYTE $0x03 // EMMS BYTE $0x0F; BYTE $0x77 RET