Edited at

ARM 関数呼び出し引数の渡し方

More than 1 year has passed since last update.

ARMで関数呼び出しの引数の渡し方について調べてみました。

arm-none-eabi-gcc で簡単なC言語のサンプルコードをアセンブラ出力して調査しました。

arm-none-eabi-gcc -S test.c

関数呼び出しの仕様はAAPCSと呼ばれるドキュメントに記載があります。


int 1個の場合

void arg1(unsigned int arg1);

void test(void)
{
arg1(1);
}

    mov r0, #1

bl arg1

mov で r0 に引数を代入します。

bl で関数にジャンプします。


int 2個の場合

void arg2(unsigned int arg1, unsigned int arg2);

void test(void)
{
arg2(1,2);
}

    mov r0, #1

mov r1, #2
bl arg2

mov で2個目の引数をr1に代入します。

ARMではintの場合は引数4個までレジスタで渡します。


int 5個の場合

void arg5(unsigned int arg1, unsigned int arg2, unsigned int arg3, unsigned int arg4, unsigned int arg5);

void test(void)
{
arg5(1,2,3,4,5);
}

    sub sp, sp, #8

mov r3, #5
str r3, [sp]
mov r0, #1
mov r1, #2
mov r2, #3
mov r3, #4
bl arg5

sp は 4バイトしか使わないけど8バイト伸ばします。

mov r3, #5 で5個目の引数をr3にレジスタに代入します。

str r3, [sp] で、それをメモリ上のスタックにストアします。


float 1個の場合

void argf(float arg1);

void test(void)
{
argf(1.0);
}

    mov r0, #1065353216

bl argf

floatは32bitなので、intの場合と同じです。


double 1個の場合

void argd(double arg1);

void test(void)
{
argd(1.1);
}

    adr r1, .L2

ldmia r1, {r0-r1}
bl argd

.L2:
.word 2576980378
.word 1072798105

double は 64bitなので r0, r1 に数値を入れます。

adr で、ラベル L2のアドレスをr1に代入し、ldmiaでr0, r1にロードします。


dobule 3個の場合

double 2個まではレジスタ渡しできます。3個目からはスタック渡しです。

void argd3(double arg1, double arg2, double arg3);

void test(void)
{
argd(1.1, 2.2, 3.3);
}

    sub sp, sp, #12

adr r4, .L2
ldmia r4, {r3-r4}
stmia sp, {r3-r4}
adr r1, .L2+8
ldmia r1, {r0-r1}
adr r3, .L2+16
ldmia r3, {r2-r3}
bl argd

.L2:
.word 1717986918
.word 1074423398
.word 2576980378
.word 1072798105
.word 2576980378
.word 1073846681

スタックを12バイト伸ばしている理由が不明。mod 8 == 0でないけど大丈夫なのでしょうか?


4バイトの構造体

struct s4{

int i1;
};

void args(struct s4 st);

void test(void)
{
struct s4 st;
args(st);
}

    sub sp, sp, #8

ldr r0, [fp, #-8]
bl args

スタックに4バイトの構造体を確保する。AAPCSではpublic interfaceの場合spは mod 8 == 0というルールなので、4バイトだけ使うが、スタックを8バイト伸ばす。

ldr で r0 に構造体をロードします。


16バイトの構造体

struct s16{

int i1;
int i2;
int i3;
int i4;
};

void args(struct s16 st);

void test(void)
{
struct s16 st;
args(st);
}

    sub sp, sp, #16

sub r3, fp, #20
ldmia r3, {r0, r1, r2, r3}
bl args

スタックに16バイトの構造体を確保する。

ldmia で r0 - r3に構造体をロードします。


20バイトの構造体

16バイト以上の構造体は、16バイトまではレジスタで渡して、16バイト以降はスタックで渡します。

struct s20{

int i1;
int i2;
int i3;
int i4;
int i5;
};

void args(struct s20 st);

void test(void)
{
struct s20 st;
args(st);
}

    sub sp, sp, #32

ldr r3, [fp, #-8]
str r3, [sp]
sub r3, fp, #24
ldmia r3, {r0, r1, r2, r3}
bl args

スタックに20バイトの構造体を確保する。引数渡しで4バイト使うので、mod 8 == 0になるように、 24 + 8 の32バイト スタックを伸ばす。

スタックに16バイト以降の4バイトをロードする。

ldmiaで r0 - r3に構造体をロードします。


document

AAPCSを読む前に、Microsoftによる「ARM ABI 規則の概要」を読むと良いです。