Practice4 Explanation - GawinGowin/libasm GitHub Wiki
Practice4: 複数引数関数プログラムの詳細解説
なぜこの練習を行うのか
学習目的
-
System V AMD64 ABI の完全理解
- 引数渡し規約の実践的習得
- レジスタ使用ルールの体得
- 実際のシステムとの互換性確保
-
実用的な関数設計
- C言語との相互運用準備
- ライブラリ関数の実装基礎
- パフォーマンス効率の追求
-
レジスタ管理の高度化
- 複数レジスタの同時使用
- 効率的なデータフロー設計
- メモリアクセス最小化
コード詳細解析
; practice4.s - 複数引数関数
section .text
global sum_six_numbers
sum_six_numbers:
; 引数: rdi, rsi, rdx, rcx, r8, r9
; 戻り値: rax
mov rax, rdi
add rax, rsi
add rax, rdx
add rax, rcx
add rax, r8
add rax, r9
ret
System V AMD64 ABI 詳細
引数渡しレジスタ順序
第1引数: RDI
第2引数: RSI
第3引数: RDX
第4引数: RCX
第5引数: R8
第6引数: R9
第7引数以降: スタック(右から左へプッシュ)
戻り値規約
整数戻り値: RAX (64bit以下)
大きな構造体: メモリ経由で返却
浮動小数点: XMM0
実装解析
初期化処理
mov rax, rdi ; 第1引数を結果レジスタに移動
重要なポイント:
rax
を累積用レジスタとして使用- 最初の値を
mov
で設定(加算ではない)
累積加算処理
add rax, rsi ; rax += 第2引数
add rax, rdx ; rax += 第3引数
add rax, rcx ; rax += 第4引数
add rax, r8 ; rax += 第5引数
add rax, r9 ; rax += 第6引数
効率性の特徴:
- メモリアクセスなし(全てレジスタ操作)
- 分岐なし(線形実行)
- 最小限の命令数
呼び出し規約の詳細理解
C言語からの呼び出し例
// main.c
extern long sum_six_numbers(long a, long b, long c,
long d, long e, long f);
int main() {
long result = sum_six_numbers(1, 2, 3, 4, 5, 6);
// result = 21
return 0;
}
コンパイラによる引数配置
; コンパイラが生成するコード(概念)
mov rdi, 1 ; 第1引数
mov rsi, 2 ; 第2引数
mov rdx, 3 ; 第3引数
mov rcx, 4 ; 第4引数
mov r8, 5 ; 第5引数
mov r9, 6 ; 第6引数
call sum_six_numbers
; rax に戻り値が格納される
7個以上の引数を持つ関数
sum_eight_numbers:
; 引数: rdi, rsi, rdx, rcx, r8, r9, [rsp+8], [rsp+16]
; 戻り値: rax
mov rax, rdi ; 第1-6引数をレジスタから
add rax, rsi
add rax, rdx
add rax, rcx
add rax, r8
add rax, r9
add rax, [rsp+8] ; 第7引数(スタックから)
add rax, [rsp+16] ; 第8引数(スタックから)
ret
スタック引数の注意点:
[rsp]
は戻りアドレス[rsp+8]
が第7引数- 16バイト境界でアライメント
学習効果
この練習で身につくスキル
-
ABI仕様の実践理解
- 標準的な関数インターフェース
- 他言語との相互運用能力
- システムライブラリとの互換性
-
効率的なコード作成
- レジスタ最適化
- 最小限の命令使用
- 高速な計算処理
-
実用的プログラミング基礎
- ライブラリ関数の設計思想
- パフォーマンスを意識した実装
- デバッグしやすいコード構造
パフォーマンス分析
命令数とサイクル数
mov rax, rdi ; 1命令, 1サイクル
add rax, rsi ; 1命令, 1サイクル
add rax, rdx ; 1命令, 1サイクル
add rax, rcx ; 1命令, 1サイクル
add rax, r8 ; 1命令, 1サイクル
add rax, r9 ; 1命令, 1サイクル
ret ; 1命令, 1-3サイクル
合計: 7命令, 約7-9サイクル
代替実装との比較
; 非効率な実装例
sum_six_numbers_slow:
push rbp
mov rbp, rsp
sub rsp, 48 ; ローカル変数用
mov [rbp-8], rdi ; メモリに保存
mov [rbp-16], rsi
mov [rbp-24], rdx
mov [rbp-32], rcx
mov [rbp-40], r8
mov [rbp-48], r9
mov rax, 0 ; 初期化
add rax, [rbp-8] ; メモリから読み込み
add rax, [rbp-16]
add rax, [rbp-24]
add rax, [rbp-32]
add rax, [rbp-40]
add rax, [rbp-48]
leave
ret
; 約20命令, 50-70サイクル(メモリアクセスによる)
発展的な実装例
可変長引数関数の基礎
; variadic_sum(int count, ...)
variadic_sum:
; 第1引数(rdi): 引数の個数
; 残りの引数: rsi, rdx, rcx, r8, r9, [stack...]
mov rax, 0 ; 合計値初期化
test rdi, rdi ; 引数数チェック
jz .done
cmp rdi, 1
jl .done
add rax, rsi ; 第2引数
cmp rdi, 2
jl .done
add rax, rdx ; 第3引数
; ... 以下同様
.done:
ret
構造体を引数とする関数
; struct Point { long x, y; };
; long distance_squared(struct Point p1, struct Point p2);
distance_squared:
; p1.x = rdi, p1.y = rsi
; p2.x = rdx, p2.y = rcx
sub rdi, rdx ; dx = p1.x - p2.x
sub rsi, rcx ; dy = p1.y - p2.y
imul rdi, rdi ; dx²
imul rsi, rsi ; dy²
add rdi, rsi ; dx² + dy²
mov rax, rdi
ret
よくある間違いとデバッグ
1. ABI違反
; 間違い: 戻り値をrdiに格納
sum_six_numbers_wrong:
mov rdi, rdi ; 無意味
add rdi, rsi
; ...
; rdi に結果があるが、戻り値はraxに入れるべき
ret
2. レジスタの誤用
; 間違い: callee-savedレジスタの破壊
sum_six_numbers_wrong2:
mov rbx, rdi ; rbxを保存せずに使用
add rbx, rsi ; 呼び出し元のrbxを破壊!
; ...
mov rax, rbx
ret
3. スタック引数の計算ミス
; 間違い: スタックオフセットの誤計算
sum_eight_numbers_wrong:
; ...
add rax, [rsp] ; これは戻りアドレス!
add rax, [rsp+4] ; 64bitなので+8が正しい
ret
実際の開発での応用
この知識により以下が実装可能になります:
-
数学ライブラリ関数
- 統計関数(平均、分散)
- 幾何計算(距離、角度)
- 線形代数演算
-
システムプログラミング
- システムコール呼び出し
- デバイスドライバインターフェース
- 最適化ライブラリ
-
高性能計算
- SIMD命令との組み合わせ
- 並列処理の基礎
- リアルタイムシステム
この練習により、実用的なアセンブリプログラミングに必要な呼び出し規約の知識が身につき、C言語など他言語との連携が可能になります。