make
コマンドで生成されるアセンブリを観察しますskeleton/step2/
ディレクトリの該当するアーキテクチャのファイルで TODO を実装します asm_amd64.s
asm_arm64.s
go test
solution/
の完成版を参照しますGo は独自のアセンブリ記法を使用します:
Go コードから生成されるアセンブリを読んで理解できるようになる
Go コンパイラが生成するアセンブリを観察し、以下を理解します:
命令 | 説明 | 例 |
MOVQ | 64ビット値の移動 |
|
ADDQ | 64ビット加算 |
|
SUBQ | 64ビット減算 |
|
RET | 関数から戻る |
|
レジスタ: AX, BX, CX, DX, SI, DI, BP, SP, R8-R15
命令 | 説明 | 例 |
MOVD | 64ビット値の移動 |
|
ADD | 加算 |
|
SUB | 減算 |
|
RET | 関数から戻る |
|
レジスタ: R0-R30, RSP (スタックポインタ)
レジスタ | 説明 |
FP | Frame Pointer - 引数と戻り値にアクセス |
SP | Stack Pointer - ローカル変数にアクセス |
SB | Static Base - グローバル変数にアクセス |
PC | Program Counter - 次の命令のアドレス |
skeleton/step1/
ディレクトリで以下のコマンドを実行:
cd skeleton/step1
# add関数のアセンブリを見る
make add
func add(a, b int) int {
return a + b
}
実際に生成されるアセンブリ(最適化なし -N -l
):
main.add STEXT nosplit size=39 args=0x10 locals=0x10
TEXT main.add(SB), NOSPLIT|ABIInternal, $16-16
PUSHQ BP // ベースポインタを保存
MOVQ SP, BP // 現在のスタックポインタを保存
SUBQ $8, SP // スタック領域を確保
MOVQ AX, main.a+24(SP) // 引数a(AXレジスタ経由)をスタックに保存
MOVQ BX, main.b+32(SP) // 引数b(BXレジスタ経由)をスタックに保存
MOVQ $0, main.~r0(SP) // 戻り値領域を初期化
ADDQ BX, AX // AX = AX + BX (実際の加算)
MOVQ AX, main.~r0(SP) // 結果をスタックの戻り値領域に保存
ADDQ $8, SP // スタック領域を解放
POPQ BP // ベースポインタを復元
RET // 関数から戻る
重要なポイント:
func sub(a, b int) int {
return a - b
}
生成されるアセンブリでは、ADDQ の代わりに SUBQ 命令が使われます:
main.sub STEXT nosplit size=39 args=0x10 locals=0x10
TEXT main.sub(SB), NOSPLIT|ABIInternal, $16-16
// ... 前処理は add と同じ ...
SUBQ BX, AX // AX = AX - BX (引き算)
// ... 後処理は add と同じ ...
RET
Go アセンブリで簡単な関数を実装できるようになる
実際にアセンブリで関数を書いて、Go から呼び出す方法を学びます。
TEXT ·FuncName(SB), NOSPLIT, $0-24
// 関数の実装
RET
TEXT
: 関数定義の開始·FuncName
: 関数名(中点 · に注意)(SB)
: Static Base からの相対NOSPLIT
: スタック拡張チェックをスキップ$0-24
: スタックサイズ-引数と戻り値のサイズ⚠️ 重要: あなたの CPU アーキテクチャに合ったファイルを編集してください:
skeleton/step2/asm_amd64.s
skeleton/step2/asm_arm64.s
アーキテクチャの確認方法:
go env GOARCH # amd64 または arm64 が表示されます
2つの int64 を足し算する関数を実装:
// func Add(a, b int64) int64
TEXT ·Add(SB), NOSPLIT, $0-24
// TODO: 実装
RET
ヒント(AMD64):
a
は a+0(FP)
でアクセス → MOVQ a+0(FP), AX
b
は b+8(FP)
でアクセス → MOVQ b+8(FP), BX
ADDQ BX, AX
ret+16(FP)
に書き込む → MOVQ AX, ret+16(FP)
ヒント(ARM64):
a
は a+0(FP)
でアクセス → MOVD a+0(FP), R0
b
は b+8(FP)
でアクセス → MOVD b+8(FP), R1
ADD R1, R0, R0
ret+16(FP)
に書き込む → MOVD R0, ret+16(FP)
2つの int64 を引き算する関数を実装:
// func Sub(a, b int64) int64
TEXT ·Sub(SB), NOSPLIT, $0-24
// TODO: 実装
RET
ヒント:
SUBQ
命令で引き算(AX = AX - BX)SUB
命令で引き算(R0 = R0 - R1)cd skeleton/step2
# 各関数のテスト
go test -v -run TestAdd
go test -v -run TestSub
# すべてのテスト
go test -v
学んだ知識は以下の場面で活用できます: