240 likes | 362 Views
マイクロプロセッサ. 第4回. 命令:マシンの言葉(その2). 関根 優年. コンピュータハードウェア内での手続きのサポート. 手続き、サブルーチン( C でいえば関数)はどうするのか?. 手続きを行うときの手順. 手続き終了後には、何も なかったように復帰可能とする. 1.手続きからアクセスできる場所にパラメータを置く 2.手続きに制御を移す 3.手続きに必要なメモリ資源を入手する 4.必要なタスクを実行する 5 . 呼び出し元のプログラムからアクセスできる 場所に結果を置く 6.制御を元の位置に戻す. 呼び出し元のプログラム のメモリ領域.
E N D
マイクロプロセッサ 第4回 命令:マシンの言葉(その2) 関根 優年
コンピュータハードウェア内での手続きのサポートコンピュータハードウェア内での手続きのサポート 手続き、サブルーチン(Cでいえば関数)はどうするのか? 手続きを行うときの手順 手続き終了後には、何も なかったように復帰可能とする 1.手続きからアクセスできる場所にパラメータを置く 2.手続きに制御を移す 3.手続きに必要なメモリ資源を入手する 4.必要なタスクを実行する 5. 呼び出し元のプログラムからアクセスできる 場所に結果を置く 6.制御を元の位置に戻す 呼び出し元のプログラム のメモリ領域 手続き用のレジスタ領域 パラメータ、結果などの保持 プロセッサ 算術 演算 手続きプログラム のメモリ領域 手続き用のメモリ領域確保 レジスタ
手続き読出しのためのレジスタ・命令 Cでいえば、 int func(int a,int b) // 引数 { : return xxx;// 返り値 } 手続き読み出しのためのレジスタ ・$a0~$a3 : 4個の引数レジスタ、パラメータを渡すために使用 ・$v0~$v1 : 2個の返り値レジスタ、結果の値を返すために使用 ・$ra : 1個の戻りアドレス・レジスタ、制御を元に戻すために使用 ①手続き用の引数を$a0~$a3に渡す ②手続きを開始する命令 : jal(jump and link)命令 手続きのアドレスにジャンプすると同時に、 次の命令のアドレスをレジスタ$raに保持する jal ProcedureAddress (PC+4をレジスタ$raに退避させる) PC(プログラムカウンタ)-現在実行中の命令アドレスが保持されるレジスタ ③手続き本体 ④手続きの返り値を$v0~$v1に渡す ⑤手続きを終了する jr $ra 次の命令アドレス(レジスタ$raに格納されている)へジャンプ
もっと多くのレジスタを使用する場合 メモリにレジスタを スピルアウト(退避) 手続きのための引数、戻り値に必要とされる レジスタ数がそれぞれ4個,2個を超えたらば? スタック プッシュ(push) スタックに データを入れる スタックポインタ を1語分減少 メモリ New reg Old reg : : : : : レジスタ の書込 Old reg : : : : : スタックポインタ (退避先のメモリ アドレス位置) スタック 領域 ポップ(pop) スタックに データを出す スタックポインタ を1語分増加 スタックポインタを動かし、 レジスタを退避するデータ構造 : : : : : レジスタ の読出 MIPSアーキテクチャでは、スタック ポインタ専用のレジスタ$spがある
例題) 他の手続きを呼出さない手続きのコンパイル例題) 他の手続きを呼出さない手続きのコンパイル 例題) 次のC言語のステートメントを、MIPSコードに変換せよ int leaf_example (int g, int h, int i, int j){ int f; f = (g + h) - (i + j); return f; } なお、4,8,12といった定数を直接加算 または減算ができるとする 答) パラメータ変数 g, h, i, jは引数レジスタ$a0,$a1,$a2,$a3、fは$s0とする Leaf_example : sub$sp,$sp,12 # スタックに3語分のスペースをとる sw $t1,8($sp) # レジスタ$t1の値を退避 sw $t0,4($sp) # レジスタ$t0の値を退避 sw $s0,0($sp) # レジスタ$s0の値を退避 add $t0,$a0,$a1# g+hが$t0に記憶 add $t1,$a2,$a3 # i+jを$t1に記憶 sub $s0,$t1,$t2 # 変数fに$t0-$t1を記憶 add $v0,$s0,$zero #fを返す プッシュ操作 関数の中での計算 ポップ操作
例題) 他の手続きを呼出さない手続きのコンパイル-2例題) 他の手続きを呼出さない手続きのコンパイル-2 lw $s0,0($sp) # レジスタ$s0の値を復元 lw $t0,4($sp) # レジスタ$t0の値を復元 lw $t1,8($sp) # レジスタ$t1の値を復元 add $sp,$sp,12 # スタックから3語分のスペースを除く jr $ra # 呼び出し元のルーチンにジャンプして戻る ここで$t0~$t7は手続き時に破壊されてもよいとした
入れ子にされた手続き 手続き内に、他の手続きさらには自分自身がいる場合はどうする? まともにやると、 手続き用の引数レジスタ $a0~$a3 関数内で計算に用いられているレジスタ 戻りアドレスレジスタ $ra が競合してしまう。 手続きの呼び出し元 に復帰不能になる 保存されるべきほかの全てのレジスタ をスタック上にプッシュする 呼び出し側 : 呼び出し後に必要となる引数レジスタ$a0~$a3、一時レジスタ $t0~$t7があれば、プッシュ 被呼び出し側: 戻りアドレスレジスタ$ra及び被呼び出し方で使用する退避レジ スタ$s0~$s7をプッシュ。 スタックポインタの$spを、スタック中に格納されるレジスタに設定 手続きから戻るときは、レジスタをメモリから復元し、スタック・ポインタを再調整する
例題) 入れ子状にリンクされた、再帰型手続きのコンパイル例題) 入れ子状にリンクされた、再帰型手続きのコンパイル 例題) 次の階乗計算のC言語のステートメントを、MIPSコードに変換せよ int fact (int n){ if(n < 1) return (1); else return (n*fact(n-1)); } 答) パラメータ変数nは引数レジスタ$a0に相当する fact : sub$sp,$sp,8 # スタックに2語分のスペースをとる sw $ra,4($sp) # 戻りアドレスの退避 sw $a0,0($sp) # 引数レジスタ$a0の退避 slt $t0,$a0,1 # n<1であるかをチェック beq $t0,$zero,L1 # n≧1であれば、L1に分岐 add $v0,$zero,1# $v0に1を返す. add $sp,$sp,8 # スタックを2語分のスペースを落とす jr $ra # 呼び出し元に戻る L1: sub $a0,$a0,1 # n=n-1 jal fact # 引数n-1を使ってfactを呼び出す lw $a0,0($sp) # 引数nの復元 lw $ra,4($sp) # 戻りアドレスを復元する add $sp,$sp,8 # スタックから2語分のスペースをおとす mul $v0,$a0,$v0 # n * fact(n - 1)を返す jr $ra # 呼び出し元に戻る プッシュ操作 ここで再帰 終了 ポップ操作 mul $s0,$s1,$s2 は乗算命令としている
MIPSのレジスタ規約 レジスタ番号1は、アセンブラ用に予約($at) レジスタ番号26-27は、オペレーティングシステム用に予約($k0-$k1) 32個のレジスタ フレームポインタ($fp): 手続き用のデータ格納領域(手続きフレーム)のポインタ 手続き中にスタックポインタが変わる場合に有効 グローバルポインタ($gp): Cでいうグローバル変数(手続きの外で定義された 変数)に相当するレジスタ
数値を超えて アルファベット文字 ー ASCIIコード(7bit) (American Standard Code for Information Interchange) 文字は1バイト(8bit)単位で扱う byte転送用コマンド load byte : メモリから1バイト読込 lb $t0,0($sp) store byte : メモリに1バイト書込 sb $t0,0($sp) 文字列 : 文字が並んだもの 自然言語をほとんど 網羅できるユニコード(16ビット) などもある
例題) 文字列コピー手続きのコンパイル 例題) 文字列コピーを表すC言語のステートメントを、MIPSコードに変換せよ void strcpy (char x[], char y[]){ int i i = 0; while((x[i]=y[i])!=0) i=i+1; } ただし、文字列の終端には null文字があるとする。 答) 配列x,yのベースアドレスは引数レジスタ$a0,$a1, iは$s0とする。 strcpy : sub$sp,$sp,4 # スタックに1語分のスペースをとる sw $s0,0($sp) # レジスタ$s0の退避 add $s0,$zero,$zero # i=0 L1: add $t1,$a1,$s0 # y[i]のアドレスを$t1に(バイトなのでiのまま) lb $t2,0($t1) # y[i]の値を$t2に add $t3,$a0,$s0 # x[i]のアドレスを$t3に sb $t2,0($t3) # x[i]=y[i] add $s0,$s0,1 # i=i+1 bne $t2,$zero,L1# もし、y[i]がヌル文字(0)でなければL1へ lw $s0,0($sp) # $s0の前の値を復元する add $sp,$sp,4 # スタックから1語分のスペースをおとす jr $ra # 呼び出し元に戻る プッシュ操作 ポップ操作
MIPSのその他のアドレシング方式 即値アドレシング方式 プログラム中において定数を使う演算が多い 例) 配列の次の要素を指すためのインデックス繰り上げ ループの繰り返し回数のカウント 入れ子になった手続き呼出しにおけるスタック調整 全体の演算に対する割合 gcc(Cコンパイラ) 52% spice(回路シミュレータ) 69% これまでのMIPS命令では 例) $spに4を足す演算 lw $t0,Addrconstant($s0) # $t0=4 add $sp,$sp,$t0 # $sp=$sp+$t0 メモリから定数を取り出さずに、計算できるとよい 即値命令 : オペランドの一つを定数とし、命令自体の中に定数を持つ
例題) アセンブリ言語の定数の機械語への翻訳例題) アセンブリ言語の定数の機械語への翻訳 例題) 次の加算の即値命令addi(add immediate)をMIPSの機械語命令 を記述せよ addi $sp,$sp,4 答) 上記の命令の機械語コードは以下のとおり op rs rt immediate 10進数 2進数 即値で表現できるのは16ビット set less than命令(immediate) slti $t0,$s2,10 # $s2<10のとき、$t0=1とする
load upper immediate命令 設計原則4 : 一般的な場合は高速化せよ オペランドが定数である演算は多数 ⇒ メモリ読み出しなしでの即値演算 定数が16ビット以上の場合の対応 load upper immediate命令 定数の上位16ビットを格納する即値命令 lui $t0,255 の機械語 即値がレジスタの上位ビットにコピー lui $t0,255の実行後のレジスタ$t0の内容
例題) 32ビットの定数のロード 例題) 次の32ビットの定数をレジスタ$s0にロードするMIPSコードを記述せよ 0000 0000 0011 1101 0000 1001 0000 0000 答) 上位16ビットを$s0にロード lui $s0,61 (上位16ビット0000 0000 0011 1101は10進数で61) 下位16ビットを$s0に追加(実際には即値で加算) addi $s0,$s0,2304 (下位16ビット0000 1001 0000 0000は10進数で2304)
分岐とジャンプにおけるアドレシング j 10000 # 10000番地にジャンプ ジャンプ命令 6ビット 26ビット 226ビットのフィールドへ分岐可能 条件分岐 命令 bne $s0,$s1,Exit # $s0と$s1が等しくないときExitへ 6ビット 5ビット 5ビット 16ビット 216ビットのフィールドへ条件分岐可能 メモリ領域の拡大に伴い、プログラムが巨大化 PC相対アドレシング方式 分岐可能なアドレス領域を拡大する方法
PC相対アドレシング方式 分岐先は、分岐命令の近くにあることが多い 現在の命令アドレス(プログラムカウンタ)位置を基準に分岐する 分岐先のアドレス=分岐命令中のレジスタ値+現在のプログラムカウンタ プログラムカウンタから±215語、相対的にアドレスを動かすことが可能 メモリ MIPSにおいても、条件分岐命令について PC相対アドレシング方式を採用 現在の プログラムカウンタ 分岐命令 ジャンプ命令(手続き呼び出しなど) PCの近くにジャンプするとは限らない ⇒ 絶対値によるアドレシング この相対的なアドレス値 を分岐命令時に指定 分岐先の アドレス MIPSでは、 (PC+4)のアドレスを基準に相対アドレシング。 相対アドレス値は語数で表す(1語=4バイト)
例題) 機械語での分岐オフセットの表記 をコンパイルしたときの次のMIPSコード を機械語表現せよ 例題) while (save[i] == k) i = i + j; Loop: add $t1,$s3,$s3 # ここは前の例題で $s3 ← i add $t1,$t1,$t1 # 出ているので省略 add $t1,$t1,$s5# lw $t0,0($t1) #save[i]をt0に格納 bne $t0,$s5,Exit # save[i]≠kでExitへ add $s3,$s3,$s4 # i=i+j j Loop # Loopへ Exit: ただし、ループの開始アドレスを80000とする 答) 80000 80004 ここが 条件分岐命令 80008 80012 80016 (PC+4)=80020 80020 に2語長つまり、 8足したアドレス (80028)へ分岐 80024 80028 分岐先のアドレス
例題) より遠くへの分岐 分岐先が216ビット以上にあるとき ⇒ 分岐先への無条件分岐を挿入し、 挿入したジャンプ先に飛ばすかどうかを条件分岐する 例題) レジスタ$s0とレジスタ$s1が等しいときに分岐する以下の命令 beq $s0,$s1,L1 をもっと遠くへ分岐可能とする命令列に置換えよ 答) bne $s0,$s1,L2# $s0≠$s1の場合は、L2へ分岐 j L1 # $s0=$s1は、L1へ分岐 L2:
MIPSのアドレシング・モードのまとめ(1) 1.即値アドレシング オペランドに命令中の定数を指定 2.レジスタアドレシング オペランドにレジスタを指定 3.ベース相対アドレシング 命令中の定数とレジスタの和で、オペランド が記憶されているメモリアドレスを指定 命令中の定数とPCの和で、オペランド が記憶されているメモリアドレスを指定 4.PC相対アドレシング 5.擬似直接アドレシング 命令中の26ビットとPCの上位ビットを 連結してジャンプアドレスを指定
MIPSのアドレシング・モードのまとめ(2) 1.即値アドレシング 2.レジスタアドレシング レジスタ 3.ベース相対アドレシング メモリ Byte / Half Word / Word + 4.PC相対アドレシング メモリ Word +
プログラムの起動 Cプログラム 配列とポインタ: アセンブリコードでの両者の違いは? コンパイラ 例) 配列を0クリアする アセンブリ言語プログラム アセンブラ オブジェクト:機械語モジュール オブジェクト:ライブラリルーチン リンカ 実行ファイル:機械語プログラム ローダ メモリ
配列バージョンのclear clear1( int array[], int size ){ int i; for( i=0; i<size; i=i+1 ) array[i]=0; } arrayは$a0, sizeは$a1にiは$t0にあるとする # i($t0)に0($zero)をコピー : move $t0,$t1($t1を$t0にコピー) move $t0,$zero # i = 0 loop1: add $t1,$t0,$t0 # $t1=i*2 add $t1,$t1,$t1 # $t1=i*4 # array[i]のアドレス値を$t2に格納 add $t2,$a0,$t1 # $t2=array[i]のアドレス # メモリ領域(アドレス array[i])に、0を格納する sw $zero,0($t2) # array[i]=0 addi $t0,$t0,1 # i=i+1 # iとsizeの比較を行い、ループの終了判定を行う($t3が比較結果) slt $t3,$t0,$a1 # (i<size)なら$t3=1 bne $t3,$zero,loop1 # (i<size)ならloop1へ ループ内でiの繰り上げを計算し、毎回アドレスを計算しなおす
ポインタバージョンのclear clear2( int *array, int size ){ int *p; for( p=&array[0]; p<&array[size]; p=p+1 ) *p=0; } arrayの開始アドレスは$a0, sizeは$a1にあり、pは$t0とする # pに置くアドレス値を$a0からコピー move $t0,$a0 # p=&array[0] # array[size]のアドレスを計算し、$t2に格納 add $t1,$a1,$a1 # $t1=size*2 add $t1,$t1,$t1 # $t1=size*4 add $t2,$a0,$t1 # $t2=array[size]のアドレス # 現在のアドレス($t0に格納)のメモリ値を0にする。 loop2: sw $zero,0($t0) # メモリ[p]=0 # $t0を4バイト(1語長)進ませる addi $t0,$t0,4 # p=p+4 # iとsizeの比較を行い、ループの終了判定を行う($t3が比較結果) slt $t3,$t0,$t2 # (p<&array[size])なら$t3=1 bne $t3,$zero,loop2 # (p<&array[size])ならloop2へ アドレスのポインタを直接繰り上げ ⇒ ループ内の命令数が7⇒4へ