Goのアセンブラ早わかりガイド

Goのアセンブラ早わかりガイド

このドキュメントは、Goコンパイラ`gc`で使用される特殊な形式のアセンブリ言語の簡単な概要です。このドキュメントは網羅的なものではありません。

このアセンブラは、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`(引数とローカル変数)および`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)`は異なるメモリロケーションです。最初のものは仮想スタックポインタ疑似レジスタを参照し、2番目のものはハードウェアの`SP`レジスタを参照します。

`SP`と`PC`が従来、物理的な番号付きレジスタのエイリアスであるマシンでは、Goアセンブラでは`SP`と`PC`という名前は依然として特別に扱われます。たとえば、`SP`への参照には、`FP`と同様にシンボルが必要です。実際のハードウェアレジスタにアクセスするには、真の`R`名を使用します。たとえば、ARMアーキテクチャでは、ハードウェア`SP`と`PC`は`R13`と`R15`としてアクセスできます。

分岐と直接ジャンプは、常にPCへのオフセット、またはラベルへのジャンプとして記述されます。

label:
	MOVW $0, R1
	JMP label

各ラベルは、それが定義されている関数内でのみ表示されます。したがって、ファイル内の複数の関数が同じラベル名を定義して使用することが許可されています。直接ジャンプと呼び出し命令は、`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(擬似)命令です。(そうでない場合、リンカは自分自身へのジャンプ命令を追加します。TEXTにはフォールスルーはありません。)シンボルの後、引数はフラグ(下記参照)とフレームサイズ(定数ですが、下記参照)です。

TEXT runtime·profileloop(SB),NOSPLIT,$8
	MOVQ	$runtime·profileloop1(SB), CX
	MOVQ	CX, 0(SP)
	CALL	runtime·externalthreadhandler(SB)
	RET

一般的なケースでは、フレームサイズの後に引数サイズが続き、マイナス記号で区切られます。(減算ではなく、単なる独特の構文です。)フレームサイズ$24-8は、関数が24バイトのフレームを持ち、呼び出し元のフレームに存在する8バイトの引数で呼び出されることを示します。TEXTNOSPLITが指定されていない場合、引数サイズを指定する必要があります。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

は、4バイト整数値の読み取り専用64バイトテーブルであるdivtab<>を宣言して初期化し、ポインタを含まない4バイトの暗黙的にゼロ化された変数であるruntime·tlsoffsetを宣言します。

ディレクティブには、1つまたは2つの引数があります。2つある場合、最初の引数はフラグのビットマスクです。これは、数値式として記述したり、加算または論理和で結合したり、人間が理解しやすいように記号的に設定したりできます。標準の#includeファイルtextflag.hで定義されているそれらの値は次のとおりです。

特別な指示

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として参照できます。したがって、レジスタR1readerへのポインタが含まれている場合、アセンブリはrフィールドをreader_r(R1)として参照できます。

これらの#define名のいずれかがあいまいな場合(たとえば、_sizeフィールドを持つ構造体)、#include "go_asm.h"は「マクロの再定義」エラーで失敗します。

ランタイム調整

ガベージコレクションを正しく実行するには、ランタイムはすべてのグローバルデータとほとんどのスタックフレーム内のポインタの位置を知る必要があります。GoコンパイラはGoソースファイルをコンパイルするときにこの情報を出力しますが、アセンブリプログラムはこれを明示的に定義する必要があります。

NOPTRフラグ(上記参照)でマークされたデータシンボルは、ランタイム割り当てデータへのポインタを含まないものとして扱われます。RODATAフラグが付いたデータシンボルは読み取り専用メモリに割り当てられるため、暗黙的にNOPTRとマークされたものとして扱われます。ポインタよりも小さい合計サイズのデータシンボルも、暗黙的にNOPTRとマークされたものとして扱われます。アセンブリソースファイルにポインタを含むシンボルを定義することはできません。このようなシンボルは、代わりにGoソースファイルで定義する必要があります。アセンブリソースは、DATAおよびGLOBLディレクティブがなくても、名前でシンボルを参照できます。一般的な経験則として、アセンブリではなくGoですべての非RODATAシンボルを定義することをお勧めします。

各関数には、引数、結果、およびローカルスタックフレーム内のライブポインタの位置を示す注釈も必要です。ポインタ結果がなく、ローカルスタックフレームまたは関数呼び出しがないアセンブリ関数の場合、唯一の要件は、同じパッケージ内のGoソースファイルに関数のGoプロトタイプを定義することです。アセンブリ関数の名前には、パッケージ名コンポーネントを含めてはなりません(たとえば、パッケージsyscallの関数Syscallは、TEXTディレクティブで同等の名前syscall·Syscallではなく、名前·Syscallを使用する必要があります)。より複雑な状況では、明示的な注釈が必要です。これらの注釈は、標準の#includeファイルfuncdata.hで定義されている擬似命令を使用します。

関数に引数と結果がない場合、ポインタ情報は省略できます。これは、TEXT命令の引数サイズ注釈$n-0によって示されます。そうでない場合、Goから直接呼び出されないアセンブリ関数の場合でも、Goソースファイルに関数のGoプロトタイプによってポインタ情報を提供する必要があります。(プロトタイプを使用すると、go vetが引数参照をチェックすることもできます。)関数の開始時、引数は初期化されていると見なされますが、結果は初期化されていないと見なされます。結果が呼び出し命令中にライブポインタを保持する場合、関数は結果をゼロにしてから擬似命令GO_RESULTS_INITIALIZEDを実行する必要があります。この命令は、結果が初期化され、スタックの移動とガベージコレクション中にスキャンされる必要があることを記録します。通常、アセンブリ関数がポインタを返さないか、呼び出し命令を含まないようにする方が簡単です。標準ライブラリのどのアセンブリ関数もGO_RESULTS_INITIALIZEDを使用していません。

関数にローカルスタックフレームがない場合、ポインタ情報は省略できます。これは、TEXT命令のローカルフレームサイズ注釈$0-nによって示されます。関数に呼び出し命令が含まれていない場合も、ポインタ情報を省略できます。そうでない場合、ローカルスタックフレームにポインタを含めてはならず、アセンブリは擬似命令NO_LOCAL_POINTERSを実行してこの事実を確認する必要があります。スタックのサイズ変更はスタックを移動することによって実装されるため、スタックポインタは関数呼び出し中に変更される可能性があります。スタックデータへのポインタでさえ、ローカル変数に保持してはなりません。

アセンブリ関数には、引数と結果のポインタ情報を提供するため、およびgo vetがそれらにアクセスするために使用されているオフセットが正しいことをチェックできるように、常にGoプロトタイプを指定する必要があります。

アーキテクチャ固有の詳細

各マシンのすべての命令とその他の詳細をリストすることは現実的ではありません。特定のマシン(たとえば、ARM)に対して定義されている命令を確認するには、ディレクトリsrc/cmd/internal/obj/armにあるそのアーキテクチャのobjサポートライブラリのソースを参照してください。そのディレクトリには、a.out.goファイルがあります。これには、次のようなAで始まる長い定数リストが含まれています。

const (
	AAND = obj.ABaseARM + obj.A_ARCHSPECIFIC + iota
	AEOR
	ASUB
	ARSB
	AADD
	...

これは、そのアーキテクチャのアセンブラとリンカが認識している命令とそのスペルのリストです。このリストでは、各命令は大文字のAで始まります。そのため、AANDはビット単位のAND命令であるAND(先頭のAなし)を表し、アセンブリソースではANDと記述されます。列挙はほとんどアルファベット順です。(アーキテクチャに依存しないAXXXは、cmd/internal/objパッケージで定義されており、無効な命令を表します)。A名のシーケンスは、マシン命令の実際のエンコードとは関係ありません。cmd/internal/objパッケージがこの詳細を処理します。

386およびAMD64アーキテクチャの両方の命令は、cmd/internal/obj/x86/a.out.goにリストされています。

アーキテクチャは、(R1)(レジスタ間接)、4(R1)(オフセット付きレジスタ間接)、$foo(SB)(絶対アドレス)などの一般的なアドレッシングモードの構文を共有します。アセンブラは、各アーキテクチャに固有のアドレッシングモードの一部(必ずしもすべてではない)もサポートしています。以下のセクションでこれらをリストします。

前のセクションの例で明らかな詳細の1つは、命令のデータが左から右に流れることです。MOVQ $0, CXCXをクリアします。このルールは、従来の表記法で反対方向を使用するアーキテクチャでも適用されます。

以下は、サポートされているアーキテクチャのGo固有の重要な詳細のいくつかについて説明します。

32ビットIntel 386

g構造体へのランタイムポインタは、MMU内の未使用の(Goに関する限り)レジスタの値を通じて維持されます。ランタイムパッケージでは、アセンブリコードにgo_tls.hを含めることができます。これは、このレジスタにアクセスするためのOSおよびアーキテクチャに依存するマクロget_tlsを定義します。get_tlsマクロは1つの引数を取り、これはgポインタをロードするレジスタです。

たとえば、CXを使用してgmをロードするシーケンスは次のようになります。

#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でも定義されています。

アドレッシングモード

コンパイラとアセンブラの-dynlinkまたは-sharedモードを使用する場合、グローバル変数などの固定メモリ位置のロードまたはストアは、CXを上書きすると想定する必要があります。したがって、これらのモードで安全に使用するには、アセンブリソースでは通常、メモリ参照間を除いてCXを使用しないようにする必要があります。

64ビットIntel 386(別名amd64)

2つのアーキテクチャは、アセンブラレベルではほぼ同じように動作します。 64ビットバージョンでmおよびgポインタにアクセスするためのアセンブリコードは、MOVLではなくMOVQを使用することを除いて、32ビット386と同じです。

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

レジスタR10R11は、コンパイラとリンカによって予約されています。

R10g(ゴルーチン)構造体を指します。 アセンブラソースコード内では、このポインタはgとして参照する必要があります。 R10という名前は認識されません。

人間とコンパイラがアセンブリを記述しやすくするために、ARMリンカは、単一のハードウェア命令では表現できない可能性のある、一般的なアドレス指定形式とDIVMODなどの疑似操作を許可します。 これらの形式は、複数の命令として実装され、多くの場合、R11レジスタを使用して一時的な値を保持します。 手書きのアセンブリはR11を使用できますが、そうするには、リンカが関数内の他の命令の実装にもそれを使用していないことを確認する必要があります。

TEXTを定義する場合、フレームサイズ$-4を指定すると、リンカに、エントリ時にLRを保存する必要のないリーフ関数であることが通知されます。

SPという名前は、常に前述の仮想スタックポインタを参照します。 ハードウェアレジスタの場合は、R13を使用します。

条件コードの構文は、MOVW.EQのように、命令にピリオドと1文字または2文字のコードを追加することです。 複数のコードを追加できます:MOVM.IA.W。 コード修飾子の順序は関係ありません。

アドレッシングモード

ARM64

R18は「プラットフォームレジスタ」であり、Appleプラットフォームで予約されています。 誤用を防ぐために、レジスタの名前はR18_PLATFORMです。 R27R28は、コンパイラとリンカによって予約されています。 R29はフレームポインタです。 R30はリンクレジスタです。

命令修飾子は、ピリオドの後に命令に追加されます。 修飾子はP(ポストインクリメント)とW(プレインクリメント)のみです:MOVW.PMOVW.W

アドレッシングモード

参考資料:Go ARM64アセンブリ命令リファレンスマニュアル

PPC64

このアセンブラは、GOARCH値ppc64およびppc64leで使用されます。

参考資料:Go PPC64アセンブリ命令リファレンスマニュアル

IBM z/Architecture、別名s390x

レジスタR10R11は予約されています。 アセンブラは、一部の命令をアセンブルするときに、それらを使用して一時的な値を保持します。

R13g(ゴルーチン)構造体を指します。 このレジスタはgとして参照する必要があります。 R13という名前は認識されません。

R15はスタックフレームを指し、通常は仮想レジスタSPFPを使用してのみアクセスする必要があります。

ロードおよびストアの複数命令は、レジスタの範囲で動作します。 レジスタの範囲は、開始レジスタと終了レジスタによって指定されます。 たとえば、LMG (R9), R5, R7は、R5R6、およびR7に、それぞれ0(R9)8(R9)、および16(R9)の64ビット値をロードします。

MVCXCなどのストレージとストレージの命令は、最初の引数として長さを指定して記述されます。 たとえば、XC $8, (R9), (R9)は、R9で指定されたアドレスにある8バイトをクリアします。

ベクトル命令が引数として長さまたはインデックスをとる場合、それは最初の引数になります。 たとえば、VLEIF $1, $16, V2は、値16をV2のインデックス1にロードします。 ベクトル命令を使用する場合は、実行時に使用できることを確認する必要があります。 ベクトル命令を使用するには、マシンにベクトル機能(機能リストのビット129)とカーネルサポートの両方がある必要があります。 カーネルサポートがない場合、ベクトル命令は効果がありません(NOP命令と同等になります)。

アドレッシングモード

MIPS、MIPS64

汎用レジスタの名前はR0からR31、浮動小数点レジスタはF0からF31です。

R30gを指すように予約されています。 R23は一時レジスタとして使用されます。

TEXTディレクティブでは、MIPSのフレームサイズ$-4またはMIPS64のフレームサイズ$-8は、リンカにLRを保存しないように指示します。

SPは仮想スタックポインタを参照します。 ハードウェアレジスタの場合は、R29を使用します。

アドレッシングモード

GOMIPS環境変数の値(hardfloatまたはsoftfloat)は、GOMIPS_hardfloatまたはGOMIPS_softfloatのいずれかを事前に定義することにより、アセンブリコードで使用可能になります。

GOMIPS64環境変数の値(hardfloatまたはsoftfloat)は、GOMIPS64_hardfloatまたはGOMIPS64_softfloatのいずれかを事前に定義することにより、アセンブリコードで使用可能になります。

サポートされていないオペコード

アセンブラはコンパイラをサポートするように設計されているため、すべてのハードウェア命令がすべてのアーキテクチャで定義されているわけではありません。コンパイラが生成しない場合、命令が存在しない可能性があります。不足している命令を使用する必要がある場合は、2つの方法があります。 1つは、アセンブラを更新してその命令をサポートすることです。これは簡単ですが、命令が再び使用される可能性が高い場合にのみ価値があります。代わりに、簡単な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