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
...
リンキング後にバイナリに何が入るかを確認するには、`````go tool objdump`````を使用します:
``````bash
$ 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アセンブラからの指針を受けていますが、独自のプログラムであるため、いくつかの違いがあります。一つは定数評価です。アセンブラ内の定数式は、元のCのような優先順位ではなく、Goの演算子の優先順位を使用して解析されます。したがって、3&1<<2
は4であり、0ではありません。これは(3&1)<<2
として解析され、3&(1<<2)
としては解析されません。また、定数は常に64ビットの符号なし整数として評価されます。したがって、-2
は整数値から2を引いたものではなく、同じビットパターンを持つ64ビットの符号なし整数です。この区別はほとんど重要ではありませんが、あいまいさを避けるために、右オペランドの高ビットが設定されている場合の除算や右シフトは拒否されます。
シンボル
擬似レジスタを参照する4つの事前宣言されたシンボルがあります。これらは実際のレジスタではなく、ツールチェーンによって維持される仮想レジスタです。たとえば、フレームポインタなどです。擬似レジスタのセットはすべてのアーキテクチャで同じです:
- `````FP`````: フレームポインタ:引数とローカル変数。
- `````PC`````: プログラムカウンタ:ジャンプと分岐。
- `````SB`````: 静的ベースポインタ:グローバルシンボル。
- `````SP`````: スタックポインタ:ローカルスタックフレーム内の最も高いアドレス。
すべてのユーザー定義シンボルは、擬似レジスタ`````FP`````(引数とローカル変数)および`````SB`````(グローバル)へのオフセットとして記述されます。
`````SB`````擬似レジスタはメモリの起点と考えることができるため、シンボル`````foo(SB)`````はメモリ内のアドレスとしての名前`````foo`````です。この形式は、グローバル関数やデータの名前を付けるために使用されます。`````<>`````を名前に追加すると、`````foo<>(SB)`````のように、名前は現在のソースファイル内でのみ可視になります。`````<>`````にオフセットを追加すると、そのシンボルのアドレスからのオフセットを参照します。したがって、`````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ビットは、`````_lo`````または`````_hi`````のサフィックスを名前に追加することで区別されます。`````arg_lo+0(FP)`````や`````arg_hi+4(FP)`````のようにです。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へのオフセットとして書かれるか、ラベルへのジャンプとして書かれます:
``````bash
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
(擬似)命令の何らかのジャンプでなければなりません。(そうでない場合、リンカーは自己ジャンプ命令を追加します。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バイトの引数で呼び出されることを示しています。これらは呼び出し元のフレームに存在します。NOSPLIT
がTEXT
に対して指定されていない場合、引数サイズを提供する必要があります。Goプロトタイプを持つアセンブリ関数では、go
vet
が引数サイズが正しいことを確認します。
シンボル名は中間ドットを使用してコンポーネントを区切り、静的ベース擬似レジスタSB
からのオフセットとして指定されます。この関数は、パッケージruntime
のGoソースからprofileloop
という単純な名前で呼び出されます。
グローバルデータシンボルは、DATA
ディレクティブの初期化のシーケンスによって定義され、その後にGLOBL
ディレクティブが続きます。各DATA
ディレクティブは、対応するメモリのセクションを初期化します。明示的に初期化されていないメモリはゼロに設定されます。DATA
ディレクティブの一般的な形式は
DATA symbol+offset(SB)/width, value
であり、指定されたオフセットと幅でシンボルメモリを指定された値で初期化します。特定のシンボルのDATA
ディレクティブは、増加するオフセットで書かれる必要があります。
たとえば、
``````bash
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
項目の場合。)関数は呼び出しスタックの最外層のフレームです。トレースバックはこの関数で停止する必要があります。
特別な命令
現在、arm64、amd64、ppc64、loong64、およびriscv64でサポートされています。たとえば、以下の`````MOVD`````命令の開始は32バイトに整列されています:
``````bash
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ソースファイルをコンパイルする際にこの情報を出力しますが、アセンブリプログラムは明示的に定義する必要があります。
各関数には、引数、結果、およびローカルスタックフレーム内の生きたポインタの位置を示す注釈も必要です。ポインタ結果がなく、ローカルスタックフレームがないか、関数呼び出しがないアセンブリ関数の場合、唯一の要件は、同じパッケージ内のGoソースファイルで関数のGoプロトタイプを定義することです。アセンブリ関数の名前にはパッケージ名のコンポーネントを含めてはいけません(たとえば、パッケージ`````syscall`````の関数`````Syscall`````は、`````·Syscall`````という名前を使用する必要があります。`````syscall·Syscall`````ディレクティブ内の同等の名前ではなく)。より複雑な状況では、明示的な注釈が必要です。これらの注釈は、標準`````#include`````ファイル`````funcdata.h`````で定義された擬似命令を使用します。
関数に引数や結果がない場合、ポインタ情報は省略できます。これは、`````$n-0`````の引数サイズ注釈によって示されます。そうでない場合、ポインタ情報は、Goソースファイル内の関数のGoプロトタイプによって提供されなければなりません。アセンブリ関数がGoから直接呼び出されない場合でも同様です。(プロトタイプは、`````go````` `````vet`````が引数参照を確認することも可能にします。)関数の開始時に、引数は初期化されていると見なされますが、結果は初期化されていないと見なされます。呼び出し命令中に結果が生きたポインタを保持する場合、関数は結果をゼロに初期化し、その後擬似命令`````GO_RESULTS_INITIALIZED`````を実行する必要があります。この命令は、結果が現在初期化され、スタック移動やガーベジコレクション中にスキャンされるべきであることを記録します。アセンブリ関数がポインタを返さないか、呼び出し命令を含まないようにする方が通常は簡単です。標準ライブラリのアセンブリ関数は`````GO_RESULTS_INITIALIZED`````を使用しません。
関数にローカルスタックフレームがない場合、ポインタ情報は省略できます。これは、`````$0-n`````のローカルフレームサイズ注釈によって示されます。関数に呼び出し命令が含まれていない場合も、ポインタ情報は省略できます。そうでない場合、ローカルスタックフレームにはポインタを含めることはできず、アセンブリはこの事実を擬似命令`````NO_LOCAL_POINTERS`````を実行することで確認する必要があります。スタックのサイズ変更はスタックを移動することによって実装されるため、スタックポインタは任意の関数呼び出し中に変更される可能性があります。スタックデータへのポインタでさえ、ローカル変数に保持されるべきではありません。
アセンブリ関数には常にGoプロトタイプを与えるべきです。これは、引数と結果のポインタ情報を提供し、`````go````` `````vet`````がそれらにアクセスするために使用されるオフセットが正しいことを確認するためです。
<a name="architectures"></a>
## アーキテクチャ固有の詳細
各マシンのすべての命令やその他の詳細をリストすることは実用的ではありません。特定のマシン、たとえばARMに対して定義されている命令を確認するには、そのアーキテクチャの`````obj`````サポートライブラリのソースを見てください。`````src/cmd/internal/obj/arm`````ディレクトリにあります。そのディレクトリには`````a.out.go`````というファイルがあり、`````A`````で始まる長い定数のリストが含まれています。次のように:
``````bash
const (
AAND = obj.ABaseARM + obj.A_ARCHSPECIFIC + iota
AEOR
ASUB
ARSB
AADD
...
`
これは、そのアーキテクチャのアセンブラとリンカーによって知られている命令とそのスペルのリストです。このリストの各命令は、初期大文字A
で始まります。したがって、AAND
はビット単位のAND命令を表し、AND
(先頭のA
なし)としてアセンブリソースに書かれます。列挙はほとんどがアルファベット順です。(アーキテクチャに依存しないAXXX
は、cmd/internal/obj
パッケージで定義されており、無効な命令を表します。)A
の名前の順序は、実際のマシン命令のエンコーディングとは関係ありません。cmd/internal/obj
パッケージがその詳細を処理します。
386およびAMD64アーキテクチャの命令はcmd/internal/obj/x86/a.out.go
にリストされています。
アーキテクチャは、(R1)
(レジスタ間接)、4(R1)
(オフセット付きレジスタ間接)、および$foo(SB)
(絶対アドレス)などの一般的なアドレッシングモードの構文を共有します。アセンブラは、各アーキテクチャに特有の一部の(必ずしもすべてではない)アドレッシングモードもサポートしています。以下のセクションでは、これらをリストします。
前のセクションの例に明らかな詳細の一つは、命令内のデータが左から右に流れることです:MOVQ
$0,
CX
はCX
をクリアします。このルールは、従来の表記が逆方向を使用するアーキテクチャでも適用されます。
サポートされているアーキテクチャに関するGo固有の重要な詳細の説明が続きます。
32ビットIntel 386
ランタイムポインタは、g
構造体の値を通じて、MMU内の他に使用されない(Goに関しては)レジスタの値を維持します。ランタイムパッケージ内で、アセンブリコードはgo_tls.h
を含むことができ、これはこのレジスタにアクセスするためのOSおよびアーキテクチャ依存のマクロget_tls
を定義します。get_tls
マクロは1つの引数を取り、g
ポインタをロードするレジスタです。
たとえば、g
とm
をCX
を使用してロードするシーケンスは次のようになります:
#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.
アドレッシングモード:
- `````(DI)(BX*2)`````: アドレス`````DI`````の位置に`````BX*2`````を加えたもの。
- `````64(DI)(BX*2)`````: アドレス`````DI`````の位置に`````BX*2`````を加え、さらに64を加えたもの。これらのモードは、スケールファクターとして1、2、4、および8のみを受け入れます。
コンパイラとアセンブラの`````-dynlink`````または`````-shared`````モードを使用する場合、グローバル変数のような固定メモリ位置のロードまたはストアは`````CX`````を上書きすることを前提としなければなりません。したがって、これらのモードで安全に使用するために、アセンブリソースは通常CXをメモリ参照の間に避けるべきです。
<a name="amd64"></a>
### 64ビットIntel 386(別名amd64)
2つのアーキテクチャは、アセンブラレベルでほぼ同じ動作をします。64ビット版で`````m`````および`````g`````ポインタにアクセスするためのアセンブリコードは、32ビット386と同じですが、`````MOVQ`````ではなく`````MOVL`````を使用します:
``````bash
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
はコンパイラとリンカーによって予約されています。
人々やコンパイラがアセンブリを書くのを容易にするために、ARMリンカーは、単一のハードウェア命令を使用して表現できない一般的なアドレッシング形式や擬似操作(`````DIV`````や`````MOD`````など)を許可します。これらの形式は、通常、`````R11`````レジスタを使用して一時的な値を保持する複数の命令として実装されます。手書きのアセンブリは`````R11`````を使用できますが、そうするには、リンカーが関数内の他の命令を実装するためにそれを使用していないことを確認する必要があります。
`````TEXT`````を定義する際に、フレームサイズ`````$-4`````を指定すると、リンカーにこの関数が`````LR`````を保存する必要がないリーフ関数であることを伝えます。
`````SP`````という名前は常に前述の仮想スタックポインタを指します。ハードウェアレジスタには`````R13`````を使用します。
条件コードの構文は、命令にピリオドと1文字または2文字のコードを追加することです。`````MOVW.EQ`````のように。複数のコードを追加することができます:`````MOVM.IA.W`````。コード修飾子の順序は無関係です。
アドレッシングモード:
- `````R0->16
`````R0<<16
R0@>16
: <<
の場合、左シフトR0
を16ビットシフトします。他のコードは->
(算術右シフト)、>>
(論理右シフト)、および@>
(右回転)です。
`````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
:これらは 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
のためのレジスタペアです。
PPC64
このアセンブラは GOARCH 値 ppc64 および ppc64le に使用されます。
参考文献: Go PPC64 Assembly Instructions Reference Manual
IBM z/Architecture, a.k.a. s390x
R10
と R11
は予約されています。アセンブラは、いくつかの命令を組み立てる際に一時的な値を保持するためにそれらを使用します。
R13
は g
(goroutine) 構造体を指します。このレジスタは g
として参照される必要があります。R13
という名前は認識されません。
R15
はスタックフレームを指し、通常は仮想レジスタ SP
と FP
を使用してのみアクセスされるべきです。
複数のレジスタに対するロードおよびストア命令は、レジスタの範囲で動作します。レジスタの範囲は、開始レジスタと終了レジスタによって指定されます。たとえば、LMG
(R9),
R5,
R7
はそれぞれ R5
、R6
、R7
に 64 ビットの値をロードします。
MVC
や XC
のようなストレージおよびストレージ命令は、長さを最初の引数として書きます。たとえば、XC
$8,
(R9),
(R9)
は R9
で指定されたアドレスの 8 バイトをクリアします。
ベクター命令が長さまたはインデックスを引数として取る場合、それは最初の引数になります。たとえば、VLEIF
$1,
$16,
V2
は V2
のインデックス 1 に値 16 をロードします。ベクター命令を使用する際は、実行時にそれらが利用可能であることを確認する必要があります。ベクター命令を使用するには、マシンはベクターファシリティ (ファシリティリストのビット 129) とカーネルサポートの両方を持っている必要があります。カーネルサポートがない場合、ベクター命令は効果がありません (それは NOP
命令と同等です)。
アドレッシングモード:
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
のいずれかを事前定義することによってアセンブリコードに利用可能になります。
Unsupported opcodes
アセンブラはコンパイラをサポートするように設計されているため、すべてのハードウェア命令がすべてのアーキテクチャに対して定義されているわけではありません:コンパイラが生成しない場合、それは存在しないかもしれません。欠落している命令を使用する必要がある場合、進める方法は 2 つあります。一つは、その命令をサポートするようにアセンブラを更新することで、これは簡単ですが、その命令が再度使用される可能性が高い場合にのみ価値があります。代わりに、単純な一回限りのケースでは、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