mipsで書く再帰

学校を休んだ時特有の隙間時間すき

 

再帰とは

 

簡単に言うなら関数の中に関数がある状態。

例えば再帰で1~Nまでの和を出すプログラムをcで書くと

 

int sigma(int n){

    int s;

    if(n==0) return 0;

    s=sigma(n-1);

    return n+=s;

    }

}

となる。再帰部分を除けばsigma(n)の中身はn += sだけ。

イメージとしてはとりあえずsigma(N)~sigma(1)までの

関数を作って待機させて、sigma(0)になったらブレイクして

待機させていた関数が逆から実行されていくといった感じですね。

 

レッツミップス

 

これをmipsで書こうと思った人間は三秒で気づくでしょうが

普通のジャンプ命令だと気が狂いそうになります。

一秒で考えつくのは0の時にj (待機させたとこ),それ以外の時にj(関数の最初)ですが

これだと0と1で無限ループします。そもそも再帰ですから

ifやforのような書き方は不可能なんですね。

 

jal

 

そこでjではなくjalを使いましょう。動作自体はジャンプ命令と同じですが

ジャンプ命令動作時のPC+4の値がレジスタraに保存してくれます。

そしてもう一つ、jrというものがあります。これも動作はジャンプですが

飛ばす先をタグではなくレジスタの中身を見て判断します。

つまり、jalで飛ぶ前の次の命令の位置をraに記憶させ

飛ばした先でjr $raとすれば、戻ってこれるわけです。

ちなみにパイプラインプロセッサでは保存の際もPCは動きますので

実際にはジャンプ命令の2つ先のPCが保存されるので気をつけましょう。

 

スタックポインタ

 

しかし、c言語と違って一回に記憶させられる戻り先は一つまでです。

何回も再帰させれば、当然途中の戻り先情報、変数の値は消えます。

高級言語を使っていたときの甘えは捨てましょう。

話を戻すとそういう時はスタックポインタに値を保存しましょう。

mipsでは$spと表記され、値はレジスタのアドレスです。

初期値はものによりますが、そのCPUがとりうる最大の値です。

使い方としては

addi $sp, $sp, -(保存したい値×4)

sw $t0, 0($sp)

sw $t0, 4($sp)

...

とこんな感じです。最大の値が初期値ですから使う時は引いていきます。

読み込む時はlwしてあげた後に$spに引いた値を足していきましょう。

ちなみに実機ではspの初期値は不定値になっている場合があるので

その時はコードの最初でちゃんとした初期値を入れてあげましょう。

 

作ってみる

 

再帰の作り方が理解できたところで実際に作ってみると

 

addi $s0, $zero, 10 #Nの値

addi $t0, $t0, 0 #sの初期値

jal sigma

nop

j next

addi $sp, $sp, -8 :sigma

sw $ra, 0($sp)

beq $s0, $zero, end

sw $s0, 4($sp)

jal sigma

addi $s0, $s0, -1

lw $ra, 0($sp)

lw $s0, 4($sp)

add $t0, $t0, s0

addi $sp, $sp, 8 :end

jr  $ra

:next

 

となります。jalを使う時は戻る前提なので

プログラムの最初でjalを使ってnopを挟んでjで

プログラム終了後の命令に飛ばす事で次に進みます。

そして、今回はパイプラインプロセッサという事で

分岐ハザードが起きることを前提にして分岐の直前に行う命令を

分岐の直後に記述しています。順序プロセッサだとバグるので注意。

それから、何もしない時に保存した値は使いません。

ですからロードを行わせずにspを進めて次に行かせます。

 

こんな感じで再帰はできます。挫折した人は再起してください。

ご精読ありがとうございました。