Goアセンブラ
GoのアセンブラはPlan 9のものを引き継いでいる。
疑似レジスタ
FP: Frame pointer
arguments and locals.
PC: Program counter
jumps and branches.
SB: Static base pointer
global symbols.
SP: Stack pointer
top of stack.
引数
引数はFP擬似レジスタからのオフセットで指定する
第一引数 0(FP)
第二引数 8(FP) ※ 64bit環境の場合
変数
変数はSP疑似レジスタからのオフセットで指定する。
SPはローカルフレームスタックの戦闘を指すので、参照するときは負のオフセットを指定する。
SPというマシンレジスタがある場合は、名前プレフィックスの有無で区別する。
x-8(SP) は疑似レジスタ
-8(S) はマシンレジスタ
名前プレフィックス
引数や変数を指すときは名前つけができる。
Goのプロトタイプを持っている関数なら、go vetでオフセットが正しいかチェックできる。
code:asm
// func add(x, y int) int
TEXT ·add(SB),NOSPLIT,$0
MOVQ x+0(FP), AX
ADDQ y+8(FP), AX
MOVQ AX, ret+16(FP)
RET
返り値が名前付きじゃない場合、 暗黙的にretが返り値の名前になる
シンボル名
Goのオブジェクトファイルとバイナリではシンボルの名前は、fmt.Printf, math/rand.Int のようにパッケージ名を.でつないだ名前になる。
アセンブラは.と/を区切り文字として使うので、中黒文字(MIDDLE DOT: U+00B7)と割り算文字(DIVISION SLASH: U+2215)がピリオドとスラッシュに置換される。
fmt.Printf → fmt·Printf
math/rand.Int → math∕rand·Int
関数
関数はTEXTディレクティブで宣言する。
シンボルのあとには、フラグとフレームサイズの定数を書く。
フレームサイズの後ろには引数のサイズをフレームサイズのあとにマイナス記号で区切ってから指定する。NOSPLITを指定していない場合に引数のサイズは必須。
$24-$8が指定された場合、24バイトのローカルフレームを持ち、8バイトの引数を持つ。
シンボルの宣言のあとに、関数の中身を書く。関数は最後にジャンプ命令でなければならない。
code:asm
// func add(x, y int) int
TEXT ·add(SB),NOSPLIT,$0-24
MOVQ x+0(FP), AX
ADDQ y+8(FP), AX
MOVQ AX, ret+16(FP)
RET
NOSPLIT
スタックオーバーフローのチェックを外す指定。
The //go:nosplit directive specifies that the next function declared in the file must not include a stack overflow check. This is most commonly used by low-level runtime sources invoked at times when it is unsafe for the calling goroutine to be preempted.
Goのランタイムだとアセンブリで書かれるような、低レベルな関数にNOSPLITが指定されている
値
値はDATAディレクティブで宣言する。
シンボルのあとにスラッシュで区切って、データサイズをバイト数で指定する。
カンマまで区切ったあとには、シンボルに束縛される値を書く。
GLOBLディレクティブ
GLOBLディレクティブはシンボルがグローバルであることを宣言する。
GLOBLディレクティブは対応するDATAディレクティブのあとに宣言しなければならない。
code:asm
DATA array+0(SB)/4, $"abc\z"
GLOBL array(SB), $4