跳轉到內容

MIPS 彙編/MIPS 細節

來自華夏公益教科書

暫存器

[編輯 | 編輯原始碼]

MIPS 有 32 個通用暫存器和另外 32 個浮點暫存器。暫存器都以美元符號 ($) 開頭。浮點暫存器命名為 $f0、$f1、...、$f31。通用暫存器既有名稱又有編號,如下所示。在 MIPS 組合語言程式設計時,最好使用暫存器名稱。

編號 名稱 註釋
$0 $zero 始終為零
$1 $at 保留給彙編器
$2, $3 $v0, $v1 分別為第一個和第二個返回值
$4, ..., $7 $a0, ..., $a3 函式的前四個引數
$8, ..., $15 $t0, ..., $t7 臨時暫存器
$16, ..., $23 $s0, ..., $s7 儲存暫存器
$24, $25 $t8, $t9 更多臨時暫存器
$26, $27 $k0, $k1 保留給核心(作業系統)
$28 $gp 全域性指標
$29 $sp 堆疊指標
$30 $fp 幀指標
$31 $ra 返回地址

一般來說,有很多暫存器可以在程式中使用:十個**臨時暫存器**和八個**儲存暫存器**,以及 arg $a 和返回值 $v 暫存器。臨時暫存器是通用暫存器,可以自由地用於算術和其他指令(稱為被覆蓋),而儲存暫存器必須在函式呼叫之間保持其值。(如果要使用一個儲存暫存器,則必須在過程進入時儲存它,並在過程退出時恢復它)。處理呼叫保留的 $s0..7 暫存器的最簡單方法是根本不碰它們。

臨時暫存器名稱都以 $t 開頭。例如,有 $t0、$t1 ... $t9。這意味著有 10 個臨時暫存器可以使用,而不用擔心儲存和恢復其內容。儲存暫存器名為 $s0 到 $s7。

**零暫存器**,名為 $zero ($0),是一個靜態暫存器:它始終包含值為零。此暫存器不能用作儲存操作的目標,因為它的值是硬連線的,不能由程式更改。

還有一些暫存器,大多數指令不能直接訪問。其中包括程式計數器 (PC),它儲存正在執行的指令的地址(由 JAL 讀取以計算返回地址,由跳轉和分支寫入),以及“hi”和“lo”暫存器,它們用於乘法和除法,其結果大於 32 位(乘法可能導致 64 位乘積,除法導致商和餘數)。有一些特殊指令用於將資料移入和移出 hi 和 lo 暫存器。

指令格式

[編輯 | 編輯原始碼]

有 3 種指令格式:R 指令、I 指令、J 指令。

R 指令採用三個引數:兩個源暫存器(**rt** 和 **rs**)和一個目標暫存器(**rd**)。R 指令使用以下格式編寫

指令 rd, rs, rt

其中每一個代表如下

rd 目標暫存器說明符
rs 源暫存器說明符
rt 源/目標暫存器說明符

例如:

add $t0, $t1, $t2

將 $t1 和 $t2 的值相加,並將結果儲存在 $t0 中。

當彙編成機器碼時,R 指令表示如下

操作碼 rs rt rd shamt func
6 位 5 位 5 位 5 位 5 位 6 位

對於 R 格式指令,**操作碼**或“操作碼”始終為零。**rs**、**rt** 和 **rd** 分別對應於兩個源暫存器和一個目標暫存器。**shamt** 用於移位指令而不是 **rt**,以簡化硬體。在彙編中,要將 $t4 中的值左移兩位並將結果放入 $t5 中

sll $t5, $t4, 2

由於所有 R 格式指令的操作碼都是零,**func** 向硬體指定要執行的確切 R 格式指令。上面的 add 示例將編碼如下

 opcode rs    rt     rd   shamt funct
 000000 01001 01010 01000 00000 100000

由於它是一個 R 格式指令,所以前六位(操作碼)為 0。接下來的 5 位對應於 rs,在本例中為 $t1。從上面的表格中,我們發現 $t1 是 $9,其二進位制表示為 01001。同樣,接下來的五位編碼為 $t2 = $10 = 01010。目標是 $t0 = $8 = 01000。我們沒有執行移位,所以 shamt 為 00000。最後,由於 add 指令的 func 為 100000。

對於上面的移位示例,操作碼欄位再次為 0,因為這是一個 R 格式指令。rs 欄位在移位中未使用,因此我們將接下來的五位保留為 0。rt 欄位為 $t4 = $12 = 01100。rd 欄位為 $t5 = $13 = 01101。移位量,shamt,為 2 = 00010。最後,sll 的 func 欄位為 000000。因此,sll $t5, $t4, 2 的編碼為

opcode rs    rt     rd   shamt  funct
000000 00000 01100 01101 00010  000000

I 指令採用兩個暫存器引數和一個 16 位“立即數”值。立即數是儲存為指令一部分而不是儲存在記憶體中的值。這使得訪問常量比將常量放在記憶體中然後載入它們快得多(因此得名)。I 格式指令與 R 格式指令一樣,首先指定目標暫存器(**rt**)。接下來是一個源暫存器(**rs**),最後是立即數。

指令 rt, rs, imm

例如,假設我們想要將值 5 新增到暫存器 $t1 中,並將結果儲存在 $t0 中

addi $t0, $t1, 5

或將比較和分支到附近的標籤(範圍為 16 位有符號位移,左移 2 位)。彙編器計算 `(目標 - 分支指令地址 + 4) >> 2` 作為立即數

beq $t0, $zero,  t0_equals_zero_branch_target

I 格式指令在機器碼中表示如下

操作碼 rs rt imm
6 位 5 位 5 位 16 位

**操作碼**指定請求的操作。**rs** 和 **rt** 各為 5 位,與 R 格式指令相同,位置也相同。**imm** 欄位儲存立即數。根據指令的不同,立即數常量可以是符號擴充套件的或零擴充套件的。如果需要 32 位立即數,則存在一個特殊的指令 **lui**(“載入上部立即數”),用於將立即數載入到暫存器的上部 16 位中。然後,該暫存器可以與另一個 16 位立即數進行邏輯 或運算,以將最終值儲存在該暫存器中。然後,該值可以在普通的 R 格式指令中使用。以下指令序列將位模式 0101 0101 0101 ... 儲存到暫存器 $t0 中

lui $t0, 0x5555
ori $t0, $t0, 0x5555

通常,彙編器會自動以這種方式拆分 32 位常量,因此如果編寫 li $t0, 0x5555555,程式設計師就不必擔心。一些彙編器(如啟用了擴充套件偽指令模式的 MARS)甚至支援 addi / addiu[檢查拼寫] / xori / 等等的較大立即數,這樣。

上面的 addi 示例將編碼如下。addi 指令的操作碼為 001000。源暫存器 $t1 的編號為 9,即二進位制的 01001。目標暫存器 $t0 的編號為 8,即二進位制的 01000。5 在二進位制中為 101,因此機器碼中的 addi $t0, $t1, 5

opcode   rs   rt           imm
001000 01001 01000 0000 0000 0000 0101

addi 和 addiu[檢查拼寫] 將其 16 位立即數符號擴充套件到 32 位(因此可以表示 -32768 .. 32767 的有符號二進位制補碼值,即 0..0x7fff 和 0xffff8000 .. 0xffffffff 的無符號值)

按位布林邏輯指令,如 andi、ori 和 xori,將立即數零擴充套件(因此可以使用 0..65535 的值)

使用 I 格式的其他指令包括載入/儲存(地址 = 暫存器 + 帶符號的 16 位位移)、立即比較(例如 slti $t1, $t0, 1234,在暫存器中生成 0 或 1)、lui 用於將立即數載入到暫存器的較高 16 位,以及分支(但不是跳轉)指令。

J 指令用於將程式流程轉移到 PC 暫存器周圍 256MB 區域內的給定硬編碼絕對地址。J 指令幾乎總是用標籤編寫:彙編器和連結器會將標籤轉換為數值。J 指令只接受一個引數:要跳轉到的地址。

(真正針對 PC 的相對控制轉移可以使用 'b label' 完成,'b label' 可用於位置無關程式碼。MIPS 分支指令是 I 格式指令,具有左移 2 位的 16 位相對位移,這與跳轉不同。)

指令 地址

有兩種 J 格式指令:jjal。後者將在後面討論。j(“跳轉”)指令告訴處理器立即跳到 地址 指示的指令。例如,要跳轉到 label1

j label1

J 格式指令被編碼為

操作碼 地址
6 位 26 位

在 MIPS32 機器上,地址是 32 位寬的,因此 26 位可能不足以指定要跳轉到的指令。幸運的是,由於所有指令都是 32 位(4 個位元組)寬的,我們可以假設所有指令都從可被 4 整除的位元組地址開始(實際上,載入程式保證了這一點)。在二進位制中,可被 4 整除的數字以兩個零結尾(就像十進位制中可被 100 整除的數字總是以兩個零結尾)。因此,我們可以允許彙編器省略最後兩個零,並讓硬體重新插入它們。這實際上使地址欄位變成了 30 位。最後 4 位將從下一條指令的地址中借用,因此我們不能讓程式跨越 256MB 邊界,因為跨越邊界的跳轉將需要改變最高 4 位。(當 J 指令正在處理時,PC 已經更新為指向下一條指令,即分支延遲槽。)

在上面的示例中,如果 label1 指定了地址為 120 或二進位制的 1111000 的指令,我們可以用機器碼對上面的跳轉示例進行編碼。j 的操作碼為 2 或二進位制的 10,我們必須截斷跳轉地址的最後兩位,將其變為 11110。因此,j 120 的機器碼如下所示。

opcode |---------------addr-----------|
000010 0000 0000 0000 0000 0000 0111 10
華夏公益教科書