Practice4 Explanation - GawinGowin/libasm GitHub Wiki

Practice4: 複数引数関数プログラムの詳細解説

なぜこの練習を行うのか

学習目的

  1. System V AMD64 ABI の完全理解

    • 引数渡し規約の実践的習得
    • レジスタ使用ルールの体得
    • 実際のシステムとの互換性確保
  2. 実用的な関数設計

    • C言語との相互運用準備
    • ライブラリ関数の実装基礎
    • パフォーマンス効率の追求
  3. レジスタ管理の高度化

    • 複数レジスタの同時使用
    • 効率的なデータフロー設計
    • メモリアクセス最小化

コード詳細解析

; 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バイト境界でアライメント

学習効果

この練習で身につくスキル

  1. ABI仕様の実践理解

    • 標準的な関数インターフェース
    • 他言語との相互運用能力
    • システムライブラリとの互換性
  2. 効率的なコード作成

    • レジスタ最適化
    • 最小限の命令使用
    • 高速な計算処理
  3. 実用的プログラミング基礎

    • ライブラリ関数の設計思想
    • パフォーマンスを意識した実装
    • デバッグしやすいコード構造

パフォーマンス分析

命令数とサイクル数

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

実際の開発での応用

この知識により以下が実装可能になります:

  1. 数学ライブラリ関数

    • 統計関数(平均、分散)
    • 幾何計算(距離、角度)
    • 線形代数演算
  2. システムプログラミング

    • システムコール呼び出し
    • デバイスドライバインターフェース
    • 最適化ライブラリ
  3. 高性能計算

    • SIMD命令との組み合わせ
    • 並列処理の基礎
    • リアルタイムシステム

この練習により、実用的なアセンブリプログラミングに必要な呼び出し規約の知識が身につき、C言語など他言語との連携が可能になります。