Practice3 Explanation - GawinGowin/libasm GitHub Wiki
-
複雑な再帰アルゴリズムの実装
- 複数の再帰呼び出しを含む関数
- 中間結果の管理方法
- より高度なスタック操作
-
制御構造の理解深化
- 複数の条件分岐
- レジスタを活用した値の保持
- 効率的な計算順序
-
アルゴリズム思考の養成
- 数学的定義からコード実装への変換
- 計算量を意識したプログラミング
- デバッグ技術の向上
; practice3.s - フィボナッチ数列
section .text
global fibonacci
fibonacci:
; 引数: rdi (n)
; 戻り値: rax
cmp rdi, 2
jl .base_case
push rbx ; rbxレジスタを保存(呼び出し規約)
push rdi
dec rdi
call fibonacci
mov rbx, rax
pop rdi
sub rdi, 2
call fibonacci
add rax, rbx
pop rbx ; rbxレジスタを復元
ret
.base_case:
mov rax, rdi
ret
F(0) = 0
F(1) = 1
F(n) = F(n-1) + F(n-2) (n >= 2)
cmp rdi, 2 ; n と 2 を比較
jl .base_case ; n < 2 なら .base_case へ
判定ロジック:
-
n < 2
の場合: F(0) = 0, F(1) = 1 -
n >= 2
の場合: 再帰計算が必要
.base_case:
mov rax, rdi ; F(0) = 0, F(1) = 1
ret
重要なポイント:
- F(0) = 0, F(1) = 1 を同時に処理
- 引数値をそのまま戻り値として使用
push rbx ; rbxレジスタを保存(呼び出し規約)
push rdi ; 元の n を保存
dec rdi ; n-1 を計算
call fibonacci ; fibonacci(n-1) を呼び出し
mov rbx, rax ; 結果を rbx に保存
処理フロー:
-
rbx
レジスタを保存(System V AMD64 ABI準拠) - 現在の引数
n
をスタックに退避 -
n-1
を計算して fibonacci を再帰呼び出し - fibonacci(n-1) の結果を
rbx
に保存
pop rdi ; 元の n を復元
sub rdi, 2 ; n-2 を計算
call fibonacci ; fibonacci(n-2) を呼び出し
add rax, rbx ; F(n-1) + F(n-2) を計算
pop rbx ; rbxレジスタを復元
ret ; 結果を返す
処理フロー:
- スタックから元の
n
を復元 -
n-2
を計算して fibonacci を再帰呼び出し - fibonacci(n-2) の結果(rax)と fibonacci(n-1) の結果(rbx)を加算
-
rbx
レジスタを復元(呼び出し規約準拠)
fibonacci(4)
├── fibonacci(3)
│ ├── fibonacci(2)
│ │ ├── fibonacci(1) → 1
│ │ └── fibonacci(0) → 0
│ │ └── 1 + 0 = 1
│ └── fibonacci(1) → 1
│ └── 1 + 1 = 2
└── fibonacci(2)
├── fibonacci(1) → 1
└── fibonacci(0) → 0
└── 1 + 0 = 1
└── 2 + 1 = 3
1. fibonacci(4) 開始
- rdi = 4, スタック: []
2. 第1回目再帰前
- push rbx, スタック: [rbx_original]
- push 4, スタック: [rbx_original, 4]
- call fibonacci(3)
3. fibonacci(3) 内で第1回目再帰前
- push rbx, スタック: [rbx_original, 4, rbx_3]
- push 3, スタック: [rbx_original, 4, rbx_3, 3]
- call fibonacci(2)
4. fibonacci(2) 内で第1回目再帰前
- push rbx, スタック: [rbx_original, 4, rbx_3, 3, rbx_2]
- push 2, スタック: [rbx_original, 4, rbx_3, 3, rbx_2, 2]
- call fibonacci(1) → return 1
5. fibonacci(2) 内で第2回目再帰前
- pop 2, スタック: [rbx_original, 4, rbx_3, 3, rbx_2]
- call fibonacci(0) → return 0
- add: 1 + 0 = 1
- pop rbx, スタック: [rbx_original, 4, rbx_3, 3]
- return 1
... (以下同様)
-
高度なスタック管理
- 複数の値の同時保存・復元
- 中間結果の適切な管理
- スタック領域の効率的な使用
-
レジスタ活用技術
- 複数レジスタでの値保持
- 一時的な値の格納方法
- レジスタ間でのデータ移動
-
呼び出し規約の理解
- System V AMD64 ABI準拠
- 呼び出し元保存レジスタの管理
- 適切なレジスタ保存・復元
-
アルゴリズム実装能力
- 数学的定義の正確な翻訳
- 効率性を考慮した実装
- デバッグ困難なコードの作成・修正
fibonacci(n) の呼び出し回数:
F(5): 15回
F(10): 177回
F(20): 21,891回
F(30): 2,692,537回
指数関数的増加:
- 時間計算量: O(φⁿ) where φ ≈ 1.618
- 空間計算量: O(n) (スタック深度)
fibonacci_iter:
; 引数: rdi (n), rsi (a=0), rdx (b=1)
test rdi, rdi
jz .return_a
dec rdi
mov rax, rsi
add rax, rdx
mov rsi, rdx
mov rdx, rax
jmp fibonacci_iter
.return_a:
mov rax, rsi
ret
; 配列を使用した結果キャッシュ
; memo[n] = fibonacci(n) の結果を保存
; 間違い: rbx の値が上書きされる
call fibonacci
mov rbx, rax ; 第1回目の結果保存
call fibonacci ; rbx が破壊される可能性
add rax, rbx ; 間違った値を使用
; 正しい実装: rbx レジスタの保存・復元
push rbx ; rbx を保存
call fibonacci
mov rbx, rax ; 第1回目の結果保存
call fibonacci ; rbx は保護されている
add rax, rbx ; 正しい値を使用
pop rbx ; rbx を復元
; 間違い: 条件によってPUSH/POPが不一致
cmp rdi, 2
jl .base_case
push rdi
; .base_case でも pop が必要?(NO)
; 間違い: F(0) = 1 として実装
cmp rdi, 1
jle .base_case
.base_case:
mov rax, 1 ; F(0) = 1 は間違い
この理解を基に以下にチャレンジできます:
-
他の数列実装
- トリボナッチ数列
- カタラン数
- ルーカス数
-
最適化手法
- 高速行列べき乗法
- ビネットの公式実装
- メモ化技法
-
大数演算
- 64bit を超える値の処理
- 多倍長整数演算
この練習により、複雑な再帰アルゴリズムの実装能力が身につき、アルゴリズムの効率性を意識したプログラミングができるようになります。
- NASM(Netwide Assembler)
- GCC(GNU Compiler Collection)
# アセンブリファイルをオブジェクトファイルにコンパイル
nasm -f elf64 practice3.s -o practice3.o
# Cファイルとアセンブリオブジェクトファイルをリンクして実行ファイルを作成
gcc practice3.c practice3.o -o practice3
# 実行
./practice3
以下のテストコードを practice3.c
として保存してください:
#include <stdio.h>
// Declaration of the assembly function
extern long fibonacci(long n);
int main() {
printf("Fibonacci sequence test:\n");
// Test cases for fibonacci function
for (int i = 0; i <= 10; i++) {
long result = fibonacci(i);
printf("fibonacci(%d) = %ld\n", i, result);
}
// Additional test cases
printf("\nAdditional test cases:\n");
printf("fibonacci(15) = %ld\n", fibonacci(15));
printf("fibonacci(20) = %ld\n", fibonacci(20));
return 0;
}
Fibonacci sequence test:
fibonacci(0) = 0
fibonacci(1) = 1
fibonacci(2) = 1
fibonacci(3) = 2
fibonacci(4) = 3
fibonacci(5) = 5
fibonacci(6) = 8
fibonacci(7) = 13
fibonacci(8) = 21
fibonacci(9) = 34
fibonacci(10) = 55
Additional test cases:
fibonacci(15) = 610
fibonacci(20) = 6765