RISC-V アセンブリを読む その1 Hello World
前回の「RISC-V アセンブリを読む その0 - takumi9のブログ」でアセンブリを読むための環境を作ったので、これから実際に様々なRISC-Vアセンブリを読んでいこうと思う。前回の記事の通り、gccを用いてコンパイルした。
Hello, World
まずは一番基本的なコード。
#include <stdio.h> int main() { printf("Hello, World"); return 0; }
000000008000226c <main>: // まずはspを-16して必要な分のフレームサイズを確保 8000226c: 1141 addi sp,sp,-16 // 返り値を退避させる 8000226e: e406 sd ra,8(sp) // 呼び出し側のフレームポインタを退避 // 呼び出し側に戻った時にフレームポインタを復帰できるように 80002270: e022 sd s0,0(sp) // 呼び出され側のフレームポインタをs0に代入 // このフレームポインタがちょうど呼び出し側と呼び出され側の境に位置している 80002272: 0800 addi s0,sp,16 // pc相対で即値を作るものの0。mov a0 zeroとかで良くない? // a0 = 80002274 80002274: 00000517 auipc a0,0x0 // a0 = 80002274+444(=1bc) = 80002430。次に呼ぶprintf関数の第一引数。 80002278: 1bc50513 addi a0,a0,444 # 80002430 <main+0x1c4> // 次のpcをraレジスタに退避させて戻って来れるようにする // さらにpcを即値の80001d66に設定してprintfに飛ぶ。 8000227c: aebff0ef jal ra,80001d66 <printf> // この後の二つの命令はreturn 0のための準備 80002280: 4781 li a5,0 80002282: 853e mv a0,a5 // 前に退避させておいたmain関数の返り値を再びロードする。 80002284: 60a2 ld ra,8(sp) // 前に退避させておいたフレームポインタも再びロードする。 80002286: 6402 ld s0,0(sp) // spをもとに戻す。 80002288: 0141 addi sp,sp,16 8000228a: 8082 ret
- スタックポインタは現在実行中の関数の低位アドレス側を指し、フレームポインタは高位アドレス側(呼び出し側に近い方)を指す。
今回アセンブリを眺めていて、無駄なアセンブリが多いと感じた。これは最適化オプションをつけずに以下のコマンドでコンパイルしていたからだと気づいた。ただ、アセンブリを色々調べるにあたっては冗長でむしろわかりやすくてよかった。次回からも最適化オプションをつけないものと最適化オプション-O3
をつけたものの両方を見てみようと思う。
$ riscv64-unknown-elf-gcc -static -mcmodel=medany -fno-common -fno-builtin-printf -nostdlib -nostartfiles -lm -lgcc -T link.ld syscall.c crt.S test0.c -o test0
最適化したHello, World
0000000080002390 <main>: 80002390: 1141 addi sp,sp,-16 80002392: 00000517 auipc a0,0x0 80002396: 05e50513 addi a0,a0,94 # 800023f0 <main+0x60> 8000239a: e406 sd ra,8(sp) 8000239c: c97ff0ef jal ra,80002032 <printf> 800023a0: 60a2 ld ra,8(sp) 800023a2: 4501 li a0,0 800023a4: 0141 addi sp,sp,16 800023a6: 8082 ret
非常に短く無駄なアセンブリはないスッキリしたものになった。
参考サイト
- コンパイラのコード生成: 繰返し - Qiita:この記事ではフレームサイズの説明などがより詳しく掲載されている。