要计算机服从指挥, 就必须用计算机的语言。计算机语言中的基本单词称为指令
,一台计算机的全部指令称为该计算机的指令集
- 精简指令集(RISC)
- 复杂指令集(CISC)
MIPS汇编不同于x86汇编,属于精简指令集,常见于路由器等一些嵌入式设备中。MIPS汇编中保留了32个通用寄存器。
之前的文章中的汇编语言是基于8086的,在计算机组成与设计
这个分类将会学习MIPS
运算操作
两个来源和一个目的地,所有的算术运算都有这种形式
指令 | 示例 | 含义 | 注释 |
---|---|---|---|
加法 | add $s1,$s2,$s3 | $s1=$s2+$s3 | 三个寄存器操作数 |
减法 | sub $s1,$s2,$s3 | $s1=$s2-$s3 | 三个寄存器操作数 |
立即数加法 | add $s1,$s2,20 | $s1=$s2+20 | 用于加常数数据 |
符合硬件简单性的设计原则,说明了硬件设计三条基本原则的第一条:设计原则1:简单源于规整
简单性倾向于规则性
-规则性使实现更简单- 简洁性使性能更高,成本更低
C code:
f = (g + h) - (i + j);
Compiled MIPS code:
add t0, g, h # temp t0 = g + h
add t1, i, j # temp t1 = i + j
sub f, t0, t1 # f = t0 - t1 =(g + h) – (i + j)
寄存器操作数 add sub
与高级语言不同,MIPS算术运算指令的操作数是很严格的,它们必须来自寄存器。
在MIPS体系结构中寄存器大小为32位
,由于32位为一组的情况经常出现,因此在MIPS体系结构中将其称为字(word)
字: 计算机中的基本访问单位,通常是32位一组,在MIPS体系结构中与寄存器大小相同。
MIPS约定书写指令时用一个$符号后面跟两个字符来表示一个寄存器。
- 用于经常访问的数据
- 32位一组,编号为0到31
- 名称
$t0, $t1, ..., $t9 用于临时寄存器
$s0, $s1, ..., $s7用于存储变量
寄存器个数限制为32个的理由可以表示为硬件设计三条基本原则中的第二条:设计原则2:越小越快
C code:
f = (g + h) - (i + j);
将变量f、g、h、i依次分配给寄存器$s0、$s1、$s2、$s3和$s4
Compiled MIPS code:
add $t0, $s1, $s2
add $t1, $s3, $s4
sub $s0, $t0, $t1
存储器操作数 lw sw
在实际应用中除了有上面例子的一两个简单变量,也可能有像数组或结构那样的复杂数据结构。
这些数据元素可能远多于计算机中寄存器的个数。
因此,这类数据是存放在存储器中的。
MIPS的算术运算指令只对寄存器进行操作,因此MIPS必须包含在存储器和寄存器之间传送数据的指令。这些指令叫做 数据传送指令
为了访问存储器中的一个字,指令必须给出存储器地址。
存储器就是一个很大的下标从0开始的一维数组,地址就相当于数组的下标
如图所示,第三个数据元素的地址为2,存放的数据为10
取数 lw
将数据从存储器复制到寄存器的数据传送指令叫取数(load)
取数指令的格式是操作码后接着目标寄存器,再后面是用来访问存储器的常数和寄存器。
常数和第二个寄存器中的值相加即得存储器地址
实际的MIPS取数指令助记符为lw
,为load word的缩写
设A是一个含有100个字的数组,编译器仍然将寄存器$s1、$s2依次分配给变量g、h
设数组A的起始地址(或称基址)存放在寄存器$s3中
编译下面的c赋值语句
C code:
g = h + A[8];
因为其中一个操作数在存储器中,所以首先必须将A[8]传送到寄存器。其地址是$s3中的基址加上该元素序号8。
取回的数据应存放在一个临时寄存器中
第一行是一种简化版的描述实际上应该是第二行(4*8=32)
lw $t0, 8($s3) # 简化版描述
lw $t0, 32($s3) # load word
一个字的地址必和它所包括的4字节中某个的地址相匹配,且连续字的地址相差4
因为MIPS是按字节编址的,所以字的起始地址必须是4的倍数。这叫对其限制。
在上面的代码中,为了得到正确的字节地址,与基址寄存器$s3相加的偏移量必须是4×8 即32,这样才能正确读到A[8]
现在A[8]已取到寄存器$t0中,下一条指令就可对$t0进行操作
add $s1, $s2, $t0
表示:将s2(题目中s2分配给了h)和t0相加,结果给s1
存数 sw
存数指令:将数据从寄存器复制到存储器。
存数指令的格式和取数指令相似:首先是操作码,接着是包含待存储数据的寄存器,然后是数组元素的偏移量,最后是基址寄存器。
实际的MIPS的存数指令为sw
,即store word的缩写
假设变量h存放在寄存器$s2中,数组A的基址放在$s3中。试编译下面的C赋值语句:
A[12] = h + A[8];
这个例题中有两个操作数在里面,我们需要先把A[8]取出来,然后把h($s2)和它相加
最后再把结果存储回A[12]
lw $t0, 32($s3) # load word
add $t0, $s2, $t0
sw $t0, 48($s3) # store word
寄存器 vs. 内存
寄存器的访问速度比内存快
对内存数据进行操作需要加载和存储
需要执行更多的指令
编译器必须尽可能多地使用寄存器中的变量
只有不常用的变量才会溢出到内存中。
寄存器优化很重要!
常数和立即数操作数 addi
程序中经常会在某个操作中使用到常数---例如:将数组的下标加1,用以指向下一个数组元素。
从目前已经介绍的指令来看,如果要使用常数需要先将其从存储器中取出来。
避免使用取数指令的另一种方法,提供其中一个操作数是常数的运算指令。这种有一个常数操作数的快速加法指令叫做加立即数
,写成addi
例如:
addi $s3, $s3, 4 # $s3 = $s3 + 4
由于MIPS支持负常数,所以MIPS中没有减立即数
的命令。只需使用一个负常数 例如:
addi $s2, $s1, -1
常数0
当你想把一个寄存器里的值移到另一个寄存器里,就要用到$zero
例如:
add $t2, $s1, $zero
可以把这行代码理解为 t2=s1+0