RISC-V アセンブリを読む その2 関数呼び出し
今回は引数や返り値がわかりやすい関数呼び出しが行われているアセンブリを精査していく。
関数呼び出し
#include <stdio.h> int add(int a, int b) { return a + b; } int main() { int c = 1; int d = 2; int e = add(c, d); printf("%d", e); return 0; }
000000008000226c <add>: 8000226c: 1101 addi sp,sp,-32 8000226e: ec22 sd s0,24(sp) 80002270: 1000 addi s0,sp,32 // 引数の値を一時レジスタに格納 80002272: 87aa mv a5,a0 80002274: 872e mv a4,a1 // とりあえずメモリに格納 80002276: fef42623 sw a5,-20(s0) 8000227a: 87ba mv a5,a4 8000227c: fef42423 sw a5,-24(s0) // 結局以下のように使うからメモリに格納する必要はないように思う 80002280: fec42783 lw a5,-20(s0) 80002284: 873e mv a4,a5 80002286: fe842783 lw a5,-24(s0) // (a + b)の計算 8000228a: 9fb9 addw a5,a5,a4 // signed extension 符号拡張 // 64bitレジスタを考えているから32bit整数を64bitに変更 8000228c: 2781 sext.w a5,a5 // 返り値をa0に格納 8000228e: 853e mv a0,a5 80002290: 6462 ld s0,24(sp) 80002292: 6105 addi sp,sp,32 80002294: 8082 ret 0000000080002296 <main>: 80002296: 1101 addi sp,sp,-32 80002298: ec06 sd ra,24(sp) // フレームポインタの退避 8000229a: e822 sd s0,16(sp) // 新しいフレームポインタの更新 8000229c: 1000 addi s0,sp,32 8000229e: 4785 li a5,1 800022a0: fef42623 sw a5,-20(s0) 800022a4: 4789 li a5,2 800022a6: fef42423 sw a5,-24(s0) 800022aa: fe842703 lw a4,-24(s0) 800022ae: fec42783 lw a5,-20(s0) // 以下二つは関数のための引数を用意している // a0が第一引数、a1が第二引数 800022b2: 85ba mv a1,a4 800022b4: 853e mv a0,a5 800022b6: fb7ff0ef jal ra,8000226c <add> // 返り値a0をa5に退避 800022ba: 87aa mv a5,a0 // 以下の二つの命令は意味がない。無駄なスピル。 800022bc: fef42223 sw a5,-28(s0) 800022c0: fe442783 lw a5,-28(s0) 800022c4: 85be mv a1,a5 800022c6: 00000517 auipc a0,0x0 800022ca: 1ba50513 addi a0,a0,442 # 80002480 <main+0x1ea> 800022ce: a99ff0ef jal ra,80001d66 <printf> // 以下二つの命令はreturn 0を作るため 800022d2: 4781 li a5,0 800022d4: 853e mv a0,a5 // 退避させていたraの復帰 800022d6: 60e2 ld ra,24(sp) // 退避させていたフレームポインタの復帰 800022d8: 6442 ld s0,16(sp) // スタックポインタの値がもとに戻る 800022da: 6105 addi sp,sp,32 800022dc: 8082 ret
最適化された関数呼び出し
0000000080002378 <add>: 80002378: 9d2d addw a0,a0,a1 8000237a: 8082 ret Disassembly of section .text.startup: 000000008000237c <main-0x18>: 8000237c: 1141 addi sp,sp,-16 8000237e: 00000517 auipc a0,0x0 80002382: 05250513 addi a0,a0,82 # 800023d0 <main+0x3c> 80002386: e406 sd ra,8(sp) 80002388: 8a3ff0ef jal ra,80001c2a <printstr> 8000238c: 60a2 ld ra,8(sp) 8000238e: 557d li a0,-1 80002390: 0141 addi sp,sp,16 80002392: 8082 ret 0000000080002394 <main>: 80002394: 1141 addi sp,sp,-16 80002396: 458d li a1,3 80002398: 00000517 auipc a0,0x0 8000239c: 06050513 addi a0,a0,96 # 800023f8 <main+0x64> 800023a0: e406 sd ra,8(sp) 800023a2: c91ff0ef jal ra,80002032 <printf> 800023a6: 60a2 ld ra,8(sp) 800023a8: 4501 li a0,0 800023aa: 0141 addi sp,sp,16 800023ac: 8082 ret
非常に最適化されたいるのはいいのだが、<main-0x18>というラベルが出現している。これはなんなのか?
関数呼び出し(引数が9個以上)
引数が8個以下の時は、a0~a7のレジスタに引数として格納して関数を呼び出す。しかし引数が9個以上となると一時レジスタが足りなくなるので、代わりにスタックポインタを使うようになる。以下が引数が10個の場合。
#include <stdio.h> int add(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) { return a + b + c + d + e + f + g + h + i + j; } int main() { int a = 1;int b = 2;int c = 3;int d = 4; int e = 5;int f = 6;int g = 7;int h = 8; int i = 9;int j =10; int fuga = add(a, b, c, d, e, f, g, h, i, j); printf("%d", fuga); return 0; }
000000008000226c <add>: 8000226c: 7179 addi sp,sp,-48 8000226e: f422 sd s0,40(sp) 80002270: 1800 addi s0,sp,48 80002272: 8eaa mv t4,a0 80002274: 8e2e mv t3,a1 80002276: 8332 mv t1,a2 80002278: 8536 mv a0,a3 8000227a: 85ba mv a1,a4 8000227c: 863e mv a2,a5 8000227e: 86c2 mv a3,a6 80002280: 8746 mv a4,a7 80002282: 87f6 mv a5,t4 80002284: fef42623 sw a5,-20(s0) 80002288: 87f2 mv a5,t3 8000228a: fef42423 sw a5,-24(s0) 8000228e: 879a mv a5,t1 80002290: fef42223 sw a5,-28(s0) 80002294: 87aa mv a5,a0 80002296: fef42023 sw a5,-32(s0) 8000229a: 87ae mv a5,a1 8000229c: fcf42e23 sw a5,-36(s0) 800022a0: 87b2 mv a5,a2 800022a2: fcf42c23 sw a5,-40(s0) 800022a6: 87b6 mv a5,a3 800022a8: fcf42a23 sw a5,-44(s0) 800022ac: 87ba mv a5,a4 800022ae: fcf42823 sw a5,-48(s0) 800022b2: fec42783 lw a5,-20(s0) 800022b6: 873e mv a4,a5 800022b8: fe842783 lw a5,-24(s0) 800022bc: 9fb9 addw a5,a5,a4 800022be: 2781 sext.w a5,a5 800022c0: fe442703 lw a4,-28(s0) 800022c4: 9fb9 addw a5,a5,a4 800022c6: 2781 sext.w a5,a5 800022c8: fe042703 lw a4,-32(s0) 800022cc: 9fb9 addw a5,a5,a4 800022ce: 2781 sext.w a5,a5 800022d0: fdc42703 lw a4,-36(s0) 800022d4: 9fb9 addw a5,a5,a4 800022d6: 2781 sext.w a5,a5 800022d8: fd842703 lw a4,-40(s0) 800022dc: 9fb9 addw a5,a5,a4 800022de: 2781 sext.w a5,a5 800022e0: fd442703 lw a4,-44(s0) 800022e4: 9fb9 addw a5,a5,a4 800022e6: 2781 sext.w a5,a5 800022e8: 4018 lw a4,0(s0) 800022ea: 9fb9 addw a5,a5,a4 800022ec: 2781 sext.w a5,a5 800022ee: 4418 lw a4,8(s0) 800022f0: 9fb9 addw a5,a5,a4 800022f2: 2781 sext.w a5,a5 800022f4: 853e mv a0,a5 800022f6: 7422 ld s0,40(sp) 800022f8: 6145 addi sp,sp,48 800022fa: 8082 ret 00000000800022fc <main>: // 以下四つの命令は基本的に毎回行われる 800022fc: 715d addi sp,sp,-80 800022fe: e486 sd ra,72(sp) 80002300: e0a2 sd s0,64(sp) 80002302: 0880 addi s0,sp,80 80002304: 4785 li a5,1 80002306: fef42623 sw a5,-20(s0) 8000230a: 4789 li a5,2 8000230c: fef42423 sw a5,-24(s0) 80002310: 478d li a5,3 80002312: fef42223 sw a5,-28(s0) 80002316: 4791 li a5,4 80002318: fef42023 sw a5,-32(s0) 8000231c: 4795 li a5,5 8000231e: fcf42e23 sw a5,-36(s0) 80002322: 4799 li a5,6 80002324: fcf42c23 sw a5,-40(s0) 80002328: 479d li a5,7 8000232a: fcf42a23 sw a5,-44(s0) 8000232e: 47a1 li a5,8 80002330: fcf42823 sw a5,-48(s0) 80002334: 47a5 li a5,9 80002336: fcf42623 sw a5,-52(s0) 8000233a: 47a9 li a5,10 8000233c: fcf42423 sw a5,-56(s0) 80002340: fd042883 lw a7,-48(s0) 80002344: fd442803 lw a6,-44(s0) 80002348: fd842303 lw t1,-40(s0) 8000234c: fdc42703 lw a4,-36(s0) 80002350: fe042683 lw a3,-32(s0) 80002354: fe442603 lw a2,-28(s0) 80002358: fe842583 lw a1,-24(s0) 8000235c: fec42503 lw a0,-20(s0) // ここで一時レジスタa5を使って引数をメモリ上のスタックに詰めていることがわかる 80002360: fc842783 lw a5,-56(s0) 80002364: e43e sd a5,8(sp) 80002366: fcc42783 lw a5,-52(s0) 8000236a: e03e sd a5,0(sp) // 上の方でt1に入れていたのでa5に入れ直す 8000236c: 879a mv a5,t1 8000236e: effff0ef jal ra,8000226c <add> 80002372: 87aa mv a5,a0 80002374: fcf42223 sw a5,-60(s0) 80002378: fc442783 lw a5,-60(s0) 8000237c: 85be mv a1,a5 8000237e: 00000517 auipc a0,0x0 80002382: 1ba50513 addi a0,a0,442 # 80002538 <main+0x23c> 80002386: 9e1ff0ef jal ra,80001d66 <printf> 8000238a: 4781 li a5,0 8000238c: 853e mv a0,a5 8000238e: 60a6 ld ra,72(sp) 80002390: 6406 ld s0,64(sp) 80002392: 6161 addi sp,sp,80 80002394: 8082 ret
これは最適化しなかったアセンブリなので、非常に無駄な命令が多くなってしまった。
次回は分岐命令。