ぱたへね

はてなダイアリーはrustの色分けができないのでこっちに来た

関数呼び出しMIPS編

MIPSの関数呼び出し規約を、gccを使って確認してみました。教科書によると、$4〜$7($a0〜$a3)が引数、$2〜$3($v0〜$v1)が戻り値として使われます。

元となるソース

違いを確認するために、引数が4個の関数と、引数が5個の関数を呼び出し、gccの出したアセンブラを見てみます。

int foo(void);
int f4(int a, int b, int c, int d);
int f5(int a, int b, int c, int d, int e);

int foo(void)
{
	int a;
	int b;

	a = f4(1,2,3,4);
	b = f5(5,6,7,8,9);

	return a + b;
}

int f4(int a, int b, int c, int d)
{
	return a + b + c + d;
}

int f5(int a, int b, int c, int d, int e)
{
	return a + b + c + d + e;
}

gccのオプションは-O2を付けました。
YUKI.N>sde-gcc -S -O2 func.c

元のソースはここ、pcspimで動作するアセンブラここです。

引数の渡し方(引数が4つ以下)

呼び出し側では、引数を$4から$7にロードし、jal命令で関数を呼び出します。このときに、呼び出し側のアドレス(戻り先)が自動的に$31に保存されます。

	li	$4,1			# 0x1
	li	$5,2			# 0x2
	li	$6,3			# 0x3
	li	$7,4			# 0x4
	sw	$31,28($sp)
	jal	f4
	sw	$16,24($sp)

呼び出された側では、$4〜$7を引数として使い、$2に戻り値を入れ、$31に保存されている戻り先に飛ぶことで、関数から戻ります。

	addu	$3,$4,$5
	addu	$2,$3,$6
	j	$31
	addu	$2,$2,$7

jの後にaddu命令がありますが、これが遅延スロットと呼ばれる物です。ジャンプの後にもう一命令実行しています。教科書の後の方で詳しくでてきますが、この例ではjの先にadduを計算し、nopを入れても同じです。

	addu	$3,$4,$5
	addu	$2,$3,$6
	addu	$2,$2,$7
	j	$31
	nop

引数の渡し方(引数が5個以上)

呼び出し側では、5つ目以降の引数(今の例だと9)をスタックに積んで、関数を呼び出します。

	move	$16,$2
	li	$2,9			# 0x9
	sw	$2,16($sp)
	li	$4,5			# 0x5
	li	$5,6			# 0x6
	li	$6,7			# 0x7
	jal	f5
	li	$7,8			# 0x8

呼び出された側では、5つ目の引数はスタックから取り出して使用します。

	addu	$8,$4,$5
	addu	$3,$8,$6
	lw	$2,16($sp)
	addu	$4,$3,$7
	j	$31
	addu	$2,$4,$2

戻りアドレスの処理

関数の呼び出しにはjal(Jump and Link)命令を使用します。教科書から説明を引用します。

Unconditionally jump to the instruction at target. Save the address of the next instruction in register $ra.
(この命令は、オペランドに対して無条件ジャンプをします。同時に、次の命令のアドレスを$ra($31)レジスタに保存します。)

呼び出された側での$31の扱いは、その関数が他の関数を呼び出しているかどうかで変わります。ある関数が別の関数を呼び出している場合(non-leaf function)、関数の中で$31を保存する必要があります。逆に呼び出していない場合(leaf function)は、保存の必要がありません。

上の例で行くと関数foo()は、f4()、f5()を呼び出しているので、non-leaf functionになります。そのため、$31の保存と復元を行います。
f4()を呼び出す前にスタックに保存し、

	sw	$31,28($sp)
	jal	f4
	sw	$16,24($sp)

foo()から戻るときに、スタックから$31へ戻り先を復元しています。

	addu	$2,$16,$2
	lw	$31,28($sp)
	lw	$16,24($sp)
	j	$31

レジスタによって、関数の中で破壊して良いレジスタ、破壊してはいけないレジスタが決められています。そのようなレジスタの詳細は、教科書のFigure2.18を見てください。

pcspimでの実行例

f4()が呼ばれた時のレジスタ値です。

$4〜$7($a0〜$a3)に引数が、$31に戻り番地が入っています。

参考

より実践的な資料はこちらにあります。

ftp://www.linux-mips.org/pub/linux/mips/doc/ABI/mipsabi.pdf