跳转到内容

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
华夏公益教科书