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`:フレームポインタ:引数とローカル変数。
- `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)`は異なるメモリロケーションです。最初のものは仮想スタックポインタ疑似レジスタを参照し、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バイトの引数で呼び出されることを示します。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
は、4バイト整数値の読み取り専用64バイトテーブルであるdivtab<>
を宣言して初期化し、ポインタを含まない4バイトの暗黙的にゼロ化された変数であるruntime·tlsoffset
を宣言します。
ディレクティブには、1つまたは2つの引数があります。2つある場合、最初の引数はフラグのビットマスクです。これは、数値式として記述したり、加算または論理和で結合したり、人間が理解しやすいように記号的に設定したりできます。標準の#include
ファイルtextflag.h
で定義されているそれらの値は次のとおりです。
-
NOPROF
= 1
(TEXT
アイテムの場合。)マークされた関数をプロファイルしません。このフラグは非推奨です。 -
DUPOK
= 2
単一のバイナリにこのシンボルの複数のインスタンスが存在しても問題ありません。リンカは、使用する重複の1つを選択します。 -
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"
は「マクロの再定義」エラーで失敗します。
ランタイム調整
ガベージコレクションを正しく実行するには、ランタイムはすべてのグローバルデータとほとんどのスタックフレーム内のポインタの位置を知る必要があります。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,
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
ポインタにアクセスするためのアセンブリコードは、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
レジスタR10
とR11
は、コンパイラとリンカによって予約されています。
R10
はg
(ゴルーチン)構造体を指します。 アセンブラソースコード内では、このポインタはg
として参照する必要があります。 R10
という名前は認識されません。
人間とコンパイラがアセンブリを記述しやすくするために、ARMリンカは、単一のハードウェア命令では表現できない可能性のある、一般的なアドレス指定形式とDIV
やMOD
などの疑似操作を許可します。 これらの形式は、複数の命令として実装され、多くの場合、R11
レジスタを使用して一時的な値を保持します。 手書きのアセンブリはR11
を使用できますが、そうするには、リンカが関数内の他の命令の実装にもそれを使用していないことを確認する必要があります。
TEXT
を定義する場合、フレームサイズ$-4
を指定すると、リンカに、エントリ時にLR
を保存する必要のないリーフ関数であることが通知されます。
SP
という名前は、常に前述の仮想スタックポインタを参照します。 ハードウェアレジスタの場合は、R13
を使用します。
条件コードの構文は、MOVW.EQ
のように、命令にピリオドと1文字または2文字のコードを追加することです。 複数のコードを追加できます:MOVM.IA.W
。 コード修飾子の順序は関係ありません。
アドレッシングモード
-
R0->16
R0>>16
R0<<16
R0@>16
:<<
の場合、R0
を16ビット左シフトします。 他のコードは、->
(算術右シフト)、>>
(論理右シフト)、および@>
(右回転)です。 -
R0->R1
R0>>R1
R0<<R1
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/Architecture、別名s390x
レジスタR10
とR11
は予約されています。 アセンブラは、一部の命令をアセンブルするときに、それらを使用して一時的な値を保持します。
R13
はg
(ゴルーチン)構造体を指します。 このレジスタはg
として参照する必要があります。 R13
という名前は認識されません。
R15
はスタックフレームを指し、通常は仮想レジスタSP
とFP
を使用してのみアクセスする必要があります。
ロードおよびストアの複数命令は、レジスタの範囲で動作します。 レジスタの範囲は、開始レジスタと終了レジスタによって指定されます。 たとえば、LMG
(R9),
R5,
R7
は、R5
、R6
、およびR7
に、それぞれ0(R9)
、8(R9)
、および16(R9)
の64ビット値をロードします。
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つは、アセンブラを更新してその命令をサポートすることです。これは簡単ですが、命令が再び使用される可能性が高い場合にのみ価値があります。代わりに、簡単な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