2006年度 計算機システム演習 第4回 2005年5月19日
caller-save (前回の方法) caller-save (呼び出し元で保存) レジスタ $t0〜$t9 foo: addi $sp, $sp, -8 : # $t0に代入 : sw $t0, 4($sp) jal bar lw $t0, 4($sp) # $t0を使った処理 addi $sp, $sp, 8 jr $ra bar: caller-save (呼び出し元で保存) レジスタ $t0〜$t9 データが入っているレジスタはサブルーチンの呼び出し元で待避・復帰 サブルーチン内では $t0〜$t9 を自由に使える
callee-save callee-save (呼び出し先で保存) レジスタ $s0〜$s7 サブルーチンで使うレジスタはサブルーチンの先頭・最後で待避・復帰 呼び出し元では $s0〜$s7 を自由に使える $ra も callee-save foo: jal bar bar: addi $sp, $sp, -4 sw $s0, 0($sp) : # $s0を使った処理 lw $s0, 0($sp) addi $sp, $sp, 4 jr $ra
効率の違い レジスタを退避・復帰する回数が変わる # $xに代入 jal foo jal bar # $xを使って計算 # $xに代入 プログラム caller-save $xはfooの前で退避し、barの後で復帰(1回) $xはfooとbarの前後で保存・復帰(2回) callee-save $xの退避・復帰はfooとbarで使うかどうかに依存(0〜2回) # $xに代入 jal foo jal bar # $xを使って計算 # $xに代入 jal foo # $xを使って計算 jal bar
例外とは? Java の例外 CPU の例外 自分で書いた catch 文の中の処理が行われる OSカーネル内で用意された処理が行われる プログラム 例外発生 try { : } catch (Exception e) { 例外処理 } 例外発生 例外処理 OSカーネル
例外の種類 プログラムの不正実行で発生するもの プログラムから明示的に発生させるもの 不正アドレスのアクセス 例: lw $t0, 0x00000001 Exception 4 [Address error in inst/data fetch] プログラムから明示的に発生させるもの OSカーネルでしか使えない 機能を利用するため ファイル入出力など サブルーチンに似ているが サブルーチンでは呼べない 例外番号 内容 8 syscall 例外 13 トラップ例外
例外処理の流れ 例外ハンドラ アドレス 0x80000180 から実行される $k0, $k1 レジスタが使える .kdata : プログラム 例外ハンドラ アドレス 0x80000180 から実行される $k0, $k1 レジスタが使える : li $v0, 5 syscall move $t0, $v0 例外発生 復帰 .kdata : .ktext 0x80000180 例外ハンドラ read_int の処理 OSカーネル
レジスタの保存 例外ハンドラ内で使うレジスタを保存 例外処理がプログラムの実行に悪影響を与えないようにする callee-save .kdata save_t0: .word 0 # $t0を保存するメモリ領域 .ktext 0x80000180 .set noat # $atを扱うことを許可する move $k1, $at # 疑似命令が使うレジスタを$k1に保存 .set at # $atを扱うことを不許可にする sw $t0, save_t0 # $t0を使うなら元の値を保存
疑似命令(再) MIPS にはない命令 命令自体はあるがオペランドが違う命令 blt, bge, ... sw $t0, A($t0) lui $at, 4097 addu $at, $at, $t0 sw $t1, 0($at) ここで例外が発生して $at が 上書きされると困る $at レジスタを使って実現
コプロセッサ メインプロセッサとは別のプロセッサが例外を管理する コプロセッサのレジスタには mfc0, mtc0 命令でアクセスできる 例外番号など コプロセッサのレジスタには mfc0, mtc0 命令でアクセスできる mfc0 CPUレジスタ, コプロセッサレジスタ コプロセッサのレジスタの内容をCPUレジスタにコピー mtc0 CPUレジスタ, コプロセッサレジスタ CPUレジスタの内容をコプロセッサのレジスタにコピー
例外の種類の判定 コプロセッサの Cause レジスタに発生した例外番号が入っている $13 レジスタ mfc0 $k0, $13 例外コード mfc0 $k0, $13 srl $k0, $k0, 2 # 2ビット右シフト andi $k0, $k0, 0x1f # 下位5ビットを取り出す
実際の例外処理 例外に応じた処理をする 不正メモリアクセス syscall エラーを表示 プログラムを終了 $v0 で指定されたサービスを実行 $a0〜$a3を引数とする $v0 に結果を代入
PC の調整 次の命令から実行再開 EPC(Exception Program Counter)レジスタに例外が発生した命令のアドレスが入っている コプロセッサの $14 レジスタ EPC の値を 4 増やせば次の命令から再開できる mfc0 $k0, $14 addiu $k0, $k0, 4 mtc0 $k0, $14
レジスタの復帰と例外ハンドラ終了 例外発生時と全てのレジスタの内容を同じにしておく必要がある syscall の場合は $v0 だけ変わる eret 命令で EPC レジスタが指すアドレスから実行を再開 lw $t0, save_t0 # $t0を復帰 .set noat # $atを扱うことを許可 move $at, $k1 # $atを復帰 .set at eret
SPIM での例外ハンドラ デフォルトで /usr/local/share/exceptions.s が使われている 変更するには -exception_file オプションをつけて xspim を起動すればよい 例: xspim -exception_file myexceptions.s Loaded: myexceptions.s と表示される
スタートアップルーチン myexceptions.s には main ルーチンを呼び出す初期ルーチンも書かなければならない .text .globl __start __start: jal main # mainを呼び出す li $v0, 10 syscall # exitシステムコール
課題1(〆切:6月4日(日)23時) myexceptions.s を作成し、引数の値を10倍するシステムコールを作れ SPIM では syscall 例外は発生しないので、代わりにトラップ例外を使用する teq $zero, $zero でトラップ例外を発生させられる li $v0, 100 # システムコール番号100 li $a0, 55 # 10倍したい値 teq $zero, $zero # トラップ例外を発生させる move $a0, $v0 li $v0, 1 syscall # 10倍した値を表示 新しいシステムコールを使うプログラム例
課題1のヒント myexceptions.s .kdata : .ktext 0x80000180 # 例外ハンドラで使用するレジスタを退避($atと$t0など) # Causeレジスタから例外番号を取得 # 例外番号が13以外ならdoneにジャンプ # システムコール番号($v0)が100以外ならdoneにジャンプ # 引数($a0)の値を10倍(mul命令)して$v0に代入 done: # EPCレジスタを4進める # 退避したレジスタを復帰 eret __start:
課題2 caller-save で書かれた次ページのコードを callee-save で書き直し、コードの効率を論じよ 0 または 1 を 8 回入力させて 8 ビットの2進数とみなし、それを 10 進数に変換するプログラム 入力1×27 + 入力2×26 + 入力3×25 + 入力4×24 + 入力5×23 + 入力1×22 + 入力7×21 + 入力8×20 結果を2倍しながら入力を足していく $s0〜$s7 の内、サブルーチン内で使わないレジスタを退避する必要はない main ルーチンもサブルーチンであることに注意せよ main で $s0〜$s7 を使うなら、最初と最後で退避・復帰する必要がある
2進10進変換プログラム .text main: addi $sp, $sp, -12 sw $ra, 0($sp) li $t0, 0 # 結果 li $t1, 0 # i=0 loop: sw $t0, 4($sp) sw $t1, 8($sp) jal read1bit lw $t1, 8($sp) lw $t0, 4($sp) sll $t0, $t0, 1 # 2倍 add $t0, $t0, $v0 addi $t1, $t1, 1 blt $t1, 8, loop move $a0, $t0 li $v0, 1 syscall lw $ra, 0($sp) addi $sp, $sp, 12 jr $ra # サブルーチン read1bit: li $v0, 5 syscall # read_int
課題3 (オプション) 乗算を行うサブルーチンを mul 命令を使わずに作成せよ 単純なアルゴリズム(筆算と同じアルゴリズム) 0011 かける数の最下位ビットが1なら かけられる数を結果に足す andi $x, $y, 0x1(最下位ビットを得る) かけられる数を1ビット左シフト sll $x, $x, 1 かける数を1ビット右シフト srl $x, $x, 1 かける数のビット数回繰り返す 0011 × 0110 0000 + 0000 0010010
課題3の難易度 以下の順に難しくなるので、できるところまでやればよい 16ビットの乗算(結果は32ビット) より高速なアルゴリズム 32ビット×32ビット(結果は64ビット) より多いビット数の乗算
注意 ~/Exercise/compsys/04/ で作業すること できるだけ感想も書いて下さい 5/22 は休講です