(四)、MIPS指令系統及匯編語言
(1)指令系統的基本知識(指令格式、尋址方式)
(2)MIPS匯編語言
4.1 指令系統的基本知識
4.1.1 指令系統概述
4.1.2 指令格式
機器指令是計算機硬件可以執行的、表示一種基本操作的二進制代碼。
-
指令格式:操作碼 + 操作數(操作數地址)
- 操作碼:指明指令的操作性質
- 操作數:指明操作數的位置(或操作數本身)
-
指令表示:
- 機器表示:二級制代碼
- 符號化表示:助記符,如
MOV AX, BX
-
操作碼結構:
-
固定長度操作碼:操作碼長度固定不變
①硬件設計簡單;②指令譯碼時間開銷較小;③指令空間效率較低
-
可變長度操作碼:操作碼長度隨指令地址數目的不同而不同
①硬件設計復雜;②指令譯碼時間開銷較大;③指令空間利用率較高
-
-
指令長度:
- 定長指令系統:如MIPS指令
- 變長指令系統:一般為字節的整數倍,如X86指令
4.1.3 尋址方式
(1) 什么是尋址?
尋址方式 就是根據 形式地址 計算出操作數 有效地址 的方法。
- 形式地址:指令給出直接的操作數的地址編碼
- 有效地址:操作數實際在內存中存儲的地址
(2) 怎么尋址?
-
尋址方式的確定
-
在操作碼中給定尋址方式:如 MIPS 指令,指令中僅有一個主(虛)存地址的,且指令中僅有幾種尋址方式。 Load/store 型機器指令屬於這種情況。
-
指令中專門的尋址方式位:如 X86 指令,指令中有多個操作數,且尋址方式各不相同,需要各自說明尋址方式。
-
-
按照指定方式進行尋址
尋址方式 示意圖 說明 操作數位置 訪問操作數所需訪存次數 立即尋址 指令中 0 寄存器直接尋址 寄存器 0 寄存器間接尋址 存儲器 1 基址尋址 存儲器 1 變址尋址 存儲器 1 相對尋址 存儲器 1 堆棧尋址 存儲器 1
4.2 MIPS匯編語言
4.2.1 MIPS指令系統
(1) MIPS R2000/R3000 寄存器結構
(2) MIPS 寄存器使用約定
- 為了保持硬件的簡單,匯編語言不使用變量,操作數都是寄存器(registers),寄存器沒有數值類型(與C語言等高級語言不同)
- MIPS有 32 個寄存器,每個寄存器存放數據的寬度為 32 bits(1個字)
(3) MIPS 指令格式
-
MIPS 只有3種指令格式,32位固定長度指令格式
原因:馮·諾伊曼計算機建立在兩個原則之上——①指令與數值的表示形式一致;②全部程序可以被存儲在內存中,像數據一樣被讀寫。為了簡化計算機系統的軟/硬件,使得適用於數據操作的內存技術完全適用於指令操作,MIPS將指令也按照數據相同的”按字存儲“的方式存儲,即一條指令占32位,同時將32位划分為不同的字段,每一個字段提供指令的一部分信息,不同的划分方式形成了不同的指令格式——R、I、J,詳細見下表。
-
MIPS 指令格式一覽表
R (Register ) I (Immediate) J(Jump) opcode:與 func 組合起來,決定該條指令名(操作符),opcode等於0時代表所有R類型指令;
rs (Source Register):通常指定存放第一個操作數;
rt (Target Register):通常指定存放第二個操作數;
rd (Destination Register):通常指定存放計算結果的寄存器;
shamt:存儲執行移位運算時要移的位數,該字段在不進行移位的指令中通常置0opcode : I類型指令中opcode可以唯一確定一條指令;
rs:表示唯一的操作數寄存器;
rt:指定存儲計算結果的寄存器;
立即數:16bits,可表示$2^{16}$ 個不同的整數值,在addi
,slti
,sltiu
等指令中中立即數通過位擴展(符號擴展)方式擴展到32位opcode
:與前兩者一致;
target address:26bits,利用字對齊,可以表示出32-bit地址的28位add
,addu
,sub
,subu
,and
,or
,jr
addi
,ori
,lui
,lw
,sw
,beq
,bnq
j
,jal
說明:5bits指定寄存器位置正好可以對應32 ($2^5$)個寄存器 說明:如果立即數超過16bits所能表示的范圍,可以借助 lui
指令——將一個16bit的立即數存入寄存器的高16位,並將寄存器的低16位置 0$New PC = {\ PC[31..28],Addr,00\}$
(4) MIPS 指令語法
- 注意 “指令語法” 與 “指令格式” 的不同:指令語法是程序員書寫的語法規范,語法是固定的,用過約定好的規則使硬件實現更簡單;而指令格式是指機器碼(二進制代碼)的格式
- 指令語法:
操作符, 目標, 源1, 源2
- 操作符:指明操作的名稱
- 目標:存放操作的結果
- 源1 (2):第一 (二) 個操作數
(5) MIPS 尋址方式
-
MIPS 沒有間接尋址,且Load/Store指令采用單一尋址模式(基址尋址)
-
MIPS 尋址方式
(6) MIPS 指令介紹
-
Load / Store 指令(取數/存儲指令)
-
格式:I 類型指令
-
分類:①取數指令:
LB
(取字節),LBU
(取不帶符號字節),LH
(取半字),LHU
(取不帶符號的半字),LW
(取字),LWL
,LWR
等;②存儲指令:SB
(存字節),SH
(存半字),SW
(存字),SWL
,SWR
等
-
-
運算指令
- 格式:R 類型指令, I 類型指令
- 分類:①算數運算:
add
,addu
,addi
,addiu
,sub
,subu
,mul
,mulu
,div
,divu
,mfhi
,mflo
等;②邏輯運算:and
,andi
,or
,ori
,xor
,xori
,nor
等;③移位運算:sll
,srl
,sra
,sllv
,srlv
,srav
等
-
跳轉指令
- 格式:J 類型指令,R 類型指令
- 分類:
j
,jal
,jr
,jalr
-
轉移指令
- 格式:I 類型指令
- 分類:
beq
(相等轉移),bne
(不等轉移),blez
(小於或等於0轉移),bgtz
(大於0轉移),bltz
(小於0轉移),bltzal
,bgezal
等
-
特殊指令
- 格式:R 類型指令
- 分類:
syscall
(系統調用),break
(斷點)
-
R類型指令編碼示例
-
使用MIPS實現swap遞歸函數
4.2.2 MIPS匯編語言詳解
(1) MIPS的數據通路與內存布局
(2) MIPS的寄存器傳送的控制邏輯
(3) MIPS 匯編語言中的 算術、邏輯、移位運算
-
整數加減法
- 加法:
add $s0, $s1, $s2
- 減法:
sub $s3, $s4, $s5
- 加法:
-
0號寄存器、立即數
-
0號寄存器:定義
$0
為數字0
,也可寫作$zero
-
立即數:即數值常量,MIPS針對立即數設置了專門指令,如
addi $s0, $s1, 10
需要注意的是,MIPS沒有立即數的減法,可以用立即數加實現
-
-
算數運算的溢出
- 可檢測出溢出異常的指令:
add
,sub
,addi
- 不會檢測出溢出異常的指令:
addu
,subu
,addiu
- MIPS中的C編譯器會使用
addu
,addiu
,subu
,即不檢查溢出異常
- 可檢測出溢出異常的指令:
-
移位運算
- 邏輯左移
sll
:左移並且補0,位移量為立即數 - 邏輯右移
srl
:右移並且補0,位移量為立即數 - 算數右移
sra
:右移並且在空位做符號擴展填充,位移量為立即數 sllv
,srlv
,srav
:移位量存儲在寄存器中,處理方式與立即數位移量類似
- 邏輯左移
(4) MIPS 匯編語言中的 數據存取
-
MIPS算術指令只能操作寄存器,不能直接操作內存,需要通過數據存取指令在內存與寄存器之間傳輸數據
-
數據存取指令
-
內存到寄存器:
lw
lw $t0, 12($s0)
:$s0
稱為基址寄存器,12
稱為偏移量 -
寄存器到內存:
sw
sw $t0, 12($s0)
:與lw
相同
-
-
字節的存取指令
lb $s0, 3($s1)
:把內存中的某個地址(3 + s1中的地址值
)所存儲的數值拷貝到s0
的低地址字節上(其余24位使用符號擴展)sb $s0, 3($s1)
:把s0
的低地址字節所存儲的數值存儲到內存中的某個地址(3 + s1中的地址值
)上
-
尋址:現代計算機按字節編址,32-bit 字地址按 4 遞增
-
字對齊:對象的起始位置一定要是字長的整數倍,故MIPS中
lw
和sw
指令的基址寄存器的值與偏移量都應該是4的倍數
(5) MIPS匯編中的 分支、循環
-
條件分支指令
beq $1, $2, Label1
:相當於C語言中的if($1==$2) goto L1;
bne $1, $2, Label1
:相當於C語言中的if($1!=$2) goto L2;
-
無條件分支指令
j Label
:無條件跳轉到標簽label所在的代碼,相當於goto Label
-
不等式指令
slt $1, $2, $3
:相當於$1 = ($2 < $3) ? 1 : 0;
slti
:slt
的立即數版本
(6) 一些小練習
-
IF - ELSE 語句
// C語言原代碼 if (i == j) f = g + h; else f = g - h;
# MIPS匯編實現代碼如下 beq $s3, $s4, Label_True # branch i==j sub $s0, $s1, $s2 # f=g-h (false) j Label_Finish # goto Fin Label_True: add $s0, $s1, $s2 # f=g+h (True) Label_Finish:
-
SWITCH 語句
// C語言原代碼 switch (k) { case 0: f = i + j; break; case 1: f = g + h; break; case 2: f = g - h; break; case 3: f = i - j; break; } // 簡化為IF-ELSE語句 if (k == 0) f = i + j; else if (k == 1) f = g + h; else if (k == 2) f = g - h; else f = i - j;
# MIPS匯編實現代碼如下 bne $s5, $0, Label1 # branch k!=0 add $s0, $s3, $s4 # k==0 so f=i+j j Label_Exit # end of case so Exit Label1: addi $t0, $s5, -1 # $t0=k-1 bne $t0, $0, Label2 # branch k!=1 add $s0, $s1, $s2 # k==1 so f=g+h j Label_Exit # end of case so Exit Label2: addi $t0, $s5, -2 # $t0=k-2 bne $t0, $0, Label3 # branch k!=2 sub $s0, $s1, $s2 # k==2 so f=g-h j Label_Exit # end of case so Exit Label3: addi $t0, $s5, -3 # $t0=k-3 bne $t0, $0, Label_Exit # branch k!=3 sub $s0, $s3, $s4 # k==3 so f=i-j Label_Exit:
-
DO - WHILE 語句
// C語言原代碼 do { g = g + A[i]; i = i + j; } while (i != h); // 改寫為GOTO語句 Label_Loop: g = g + A[i]; i = i + j; if (i != h) goto Label_Loop;
# MIPS匯編實現代碼如下 Label_Loop: sll $t1, $s3, 2 #$t1= 4*i add $t1, $t1, $s5 #$t1=addr A lw $t1, 0($t1) #$t1=A[i] add $s1, $s1, $t1 #g=g+A[i] bne $s3, $s2, Label_Loop #if i!= h goto Label_Loop
(7) MIPS 支持函數功能的指令
-
可同時執行跳轉和存儲返回地址的指令
jal Label
:執行步驟 - ①link:將下一條指令地址存入$ra
;②jump:向指定的Label
跳轉- 一般配合
jr $ra
使用,即jal Label
存指令到$ra
,並跳轉到調用函數的頭部,函數執行完畢后使用jr $ra
跳轉到調用處的下一條指令,完成一次函數的調用
-
嵌套調用的實現
- 嵌套調用會覆蓋
$ra
中的返回地址信息,故需要另尋他路——使用 Stack 棧
- 嵌套調用會覆蓋
-
寄存器
$sp
始終指向棧空間最后被使用的位置(可以理解為”棧指針“,棧從下往上遞增,棧指針地址遞減)- 調用規則:①將需要保存的值壓入棧中;②指定參數(如果需要的話);③
jal
調用函數;④從棧中恢復相關的值 - 調用過程中的規則:①通過
jal
指令調用 , 使用jr $ra
指令返回;②最多可接受4個入口參數——$a0
,$a1
,$a2
,$a3
;③
# C語言 原代碼 int sumSquare(int x, int y) { return mult(x, x) + y; } # MIPS匯編實現 sumSqure: addi $sp, $sp, -8 # space on stack sw $ra, 4($sp) # save ret addr sw $a1, 0($sp) # save y add $a1, $a0, $zero # prep args jal mult # call mult lw $a1, 0($sp) # restore y add $v0, $v0, $a1 # mult()+y lw $ra, 4($sp) # get ret addr addi $sp, $sp, 8 # restore stack jr $ra mult: #mult函數
- 調用規則:①將需要保存的值壓入棧中;②指定參數(如果需要的話);③
(8) MIPS 通用寄存器使用規范
- 通用寄存器分配(見 4.2.1(2)寄存器使用約定 )
- 特殊寄存器(系統維護)
$at
:編譯器隨時可能使用,最好不要用$k0~$k1
:操作系用隨時可能使用,最好不要用$gp,$fp
:自動維護,無需操作
- 保存寄存器(程序員維護)
$0
:不能改變,恆為 0$s0-$s7
:如果被修改了需要恢復。如果被調用函數改變了這些寄存器的值,則必須在函數返回之前將這些寄存器的原始值恢復$sp
:如果被修改了需要恢復。棧指針在jal
執行之前或之后必須是指向的同一地址,不然調用函數無法從棧里正確恢復數據
- 易變寄存器(程序員維護)
$ra
:會改變。jal
會自動更改這個寄存器值,但調用函數需要手動將其值保存在棧上$v0~$v1
:會改變。始終保存最新的返回值$a0~$a3
:會改變。調用函數如果在調用完成后還要用到這些寄存器中的值,就要在調用前將這些值保存在自己的棧空間內$t0~$t7
:會改變。任何函數在任何時候都可以更新這些寄存器中的值;與$a0~$a3
類似,調用函數時需手動將這些值保存在棧空間內以防止丟失(被覆蓋)
(9) MIPS 匯編程序
-
匯編語言語句
- 可執行指令:為處理器生成在運行時執行的機器碼,告訴處理器該做什么
- 偽指令、宏:由匯編程序翻譯成真正的指令,簡化編程人員的工作
- 匯編偽指令:當翻譯代碼時為匯編程序提供信息,用來定義段、分配內存變量等;不可執行 ——匯編偽指令不是指令集的一部分
-
程序模板
-
.DATA
:偽指令,定義程序的數據段,程序變量需要在該偽指令下定義,匯編程序會分配和初始化變量的存儲空間 -
.TEXT
:定義程序的代碼段 -
.GLOBL
:偽指令,聲明 一個符號為全局的,可被其它文件引用,通常用來聲明一個程序的 main 過程
-
-
數據定義
-
目的:為變量的存儲划分內存(可能會有選擇的為數據分配標簽)
-
語法:[名字:] 偽指令 初始值 [, 初始值 ] ……
-
數據偽指令
.BYTE
:以 8 位字節存儲數值表.HALF
:以 16 位(半字長)存儲數值表.WORD
:以 32 位(一個字長)存儲數值表.WORD w:n
:將 32 位數值 w 存入 n 個邊界對齊的連續的字中.FLOAT
:以 單精度浮點數存儲數值表.DOUBLE
:以 雙精度浮點數存儲數值表 -
字符串偽指令
.ASCII
:為 一個 ASCII 字符串分配字節序列.ASCIIZ
:與 .ASCII 偽指令類似 , 但字符串 以 NULL 結尾.SPACE n
:為 數據段中 n 個未初始化的字節分配空間 -
實例
.DATA var1: .BYTE 1, 2,'Z' str1: .ASCIIZ "My String\n" var2: .WORD 0x12345678
匯編程序會為標簽(也可理解為變量)構建符號表,為每一個數據段的標簽計算地址,如下圖。
-
-
內存對齊、字節序
-
.ALIGN n
- 對下一個定義的數據做 $2^n$ 字節對齊 -
對齊:字的地址是4的整數倍(即地址的2位最低有效位必須是 $00_b$),半字的地址是2的整數倍
-
字節序、端
①小端字節排序:內存地址 = 最低有效字節 的地址,例子 : Intel IA-32, Alpha
②大端字節排序:內存地址 = 最高有效字節 的地址,例子 : SPARC, PA-RISC
▲注意:MIPS 可以操作以上兩種字節序
-
-
系統調用
-
syscall
:MIPS提供特殊的syscall
指令,從操作系統獲取服務 -
作用:程序通過系統調用實現輸入輸出
-
syscall
系統服務流程:①從$v0
寄存器中讀取服務數;②從$a0
,$a1
等寄存器中讀取參數值(如果有)③發送syscall
指令;④從結果寄存器中取回返回值(如果有) -
程序示例 與 系統服務列表
move $a0, $s0 #copy value of $s0 to $a0 li $v0, 1 #load the service number syscall #system service start
-
-
參數傳遞
-
寄存器方法(使用通用寄存器
$a0~$a3
) -
棧方法(使用棧
$sp
)棧適用於以下情況:①不適用使用寄存器時,用來存儲變量 / 數據結構;②過程調用中保存和恢復寄存器;③實現遞歸
-
(10) 一些小練習
-
選擇排序
# Objective: Sort array using selection sort algorithm # Input: $a0 = pointer to first, $a1 = pointer to last # Output: array is sorted in place ########################################################## sort: addiu $sp, $sp, 4 # allocate one word on stack sw $ra, 0($sp) # save return address on stack top: jal max # call max procedure lw $t0, 0($a1) # $t0 = last value sw $t0, 0($v0) # swap last and max values sw $v1, 0($a1) addiu $a1, $a1, 4 # decrement pointer to last bne $a0, $a1, top # more elements to sort lw $ra, 0($ sp) # pop return address addiu $sp, $sp, 4 jr $ra # return to caller # Objective: Find the address and value of maximum element # Input: $a0 = pointer to first, $a1 = pointer to last # Output: $v0 = pointer to max, $v1 = value of max ########################################################## max: move $v0, $a0 # max pointer = first pointer lw $v1, 0($v0) # $v1 = first value beq $a0, $a1, ret # if (first == last) return move $t0, $a0 # $t0 = array pointer loop: addi $t0, $t0, 4 # point to next array element lw $t1, 0($t0) # $t1 = value of A[ ble $t1, $v1, skip # if (A[i] <= max) then skip move $v0, $t0 # found new maximum move $v1, $t1 skip: bne $t0, $a1, loop # loop back if more elements ret: jr $ra
-
遞歸過程
int fact(int n) { if (n < 2) return 1; else return (n*fact(n-1)); }
fact: slti $t0,$a0,2 # (n<2)? beq $t0,$0,else # if false branch to else li $v0,1 # $v0 = 1 jr $ra # return to caller else: addiu $sp,$sp, -8 # allocate 2 words on stack sw $a0,4($sp) # save argument n sw $ra,0($sp) # save return address addiu $a0,$a0, -1 # argument = n-1 jal fact # call fact(n-1) lw $a0,4($sp) # restore argument lw $ra,0($sp) # restore return address mul $v0,$a0,$v0 # $v0 = n*fact(n-1) addi $sp,$sp,8 # free stack frame jr $ra # return to caller