パイプライン実行と例外処理

例外(Exception)には、実行中の命令とは関係なく発生する割り込み(Interrupt)と実行中の命令で異常が発生したことを知らせるトラップ(Trap)がある。割り込みは、リセットボタンが押されたというようなケースや、外部からの信号の到着、あるいは処理時間の長いI/O処理の完了の通知などで発生する。割り込みが発生すると、プロセサは実行中のプログラムを中断し、リセットを行ったり、外部入力の受け取りなどの割り込み原因に対応した処理を行う必要がある。

一方、トラップは、未定義命令を実行しようとしたり、割り算の分母がゼロというように、実行しようとする命令が原因で異常が起こる場合に発生させる例外である。この場合も実行中のプログラムを中断し、トラップ原因に対応する処理を行う必要がある。

このような例外の発生に伴い、例外処理を行うプログラムの入り口に分岐する必要がある。それは良いのであるが、問題は、割り込み処理が終了すると、中断したプログラムの実行に戻り、再開されたプログラムは、割り込み処理が無かったかのように実行を続ける必要があるという点である。

1つひとつの命令を順次実行している場合は、例外が発生した時点で実行中の命令が終了した時点で、次の命令を指すPCをセーブしておき、例外処理が終わった時点で、このアドレスから実行を再開すればよい。

しかし、パイプライン実行の場合は、次に述べるように、多少、制御が複雑になる。図4.19の第4サイクルで割り込みが入ってきた場合は、割り込み時点で実行を開始しているクリーム色をつけた命令1と命令2は、終了するまで実行し、一方、デコードステージにある命令3は実行をキャンセルする。これは、実行パイプラインに投入された命令はそのままにして、新たな命令の投入を止めるという処理が必要になる。

図4.19 割り込みの処理

そして、PCが指すキャンセルされた命令3のアドレスを、割り込み処理が終了したときに実行を再開する命令アドレスとしてセーブし、実行中の命令が全て完了するのを待って、既定の割り込み処理ルーチンの先頭アドレスにジャンプする。

割り込み処理ルーチンでは、汎用レジスタの退避などを行ってから完了を通知してきたI/Oからのデータの受け取りなどの処理を行い、必要な処理が終わると、汎用レジスタなどを復元して、セーブした再開アドレスにジャンプする。という方法で、命令3から実行を継続することができる。

ただし、この時点では命令3はデコードステージには残っていないので、再開アドレスをメモリからの命令読み出しアドレスとして、命令フェッチからやり直す必要がある。

一方、トラップは、定義されていない未定義命令を実行しようとしたというような場合や、割り算命令の分母がゼロであったというような場合に発生させ、異常処理プログラムへジャンプする。未定義命令のようなケースは命令デコードステージで検出できるので、その命令のアドレスを再開アドレスとしてセーブして、異常処理ルーチンへ分岐する。

ゼロ割り算のトラップのような場合は、異常は、命令デコード時には分からず、命令実行ステージで異常が検出されるので、PCが指す次の命令アドレスを再開アドレスとしてセーブする。

図4.20 分岐処理中に例外が発生したケース

図4.20のように分岐命令の処理中である第3、第4サイクルで例外が発生した場合は注意が必要である。第3サイクルで例外が発生した場合は、すでに実行を開始したBR命令は完了するまで実行するが、デコード状態にある実行されない命令1はストールして実行されない。そして、例外処理プログラムの先頭アドレスにジャンプする。

そうすると、BR命令が実行完了した時点で、分岐の成立、不成立に拘わらず、PCは正しい命令を指しており、このアドレスをリターンアドレスとしてセーブすることにより、例外処理の完了後に元の命令フローに戻ることができる。

また、第4サイクルに例外が発生した場合は実行を開始してしまった実行されない命令1をフラッシュする必要があるが、BR命令は実行を完了するので、第3サイクルで例外が発生した場合と同様に、リターンアドレスとしてPCをセーブして例外処理を実行すれば良い。

以上のように、例外処理は大量のハードウェアを必要とするものではないが、バグの出やすい部分であり、パイプライン処理を行う場合には注意深い設計が必要である。