计算机中指令的表示
人操作计算机的方式与计算机看到指令的方式是不同的。
指令在计算机内部是以若干或高或低的电信号的序列表示的,并且形式上和数的表示相同。
几乎所有的指令都要用到寄存器,所以必须有一套规定,以将寄存器名字映射成数字。
在MIPS汇编语言中寄存器$t0-$t7映射到寄存器8-15
寄存器$t8-$t9映射到寄存器24-25
寄存器$s0-$s7映射到寄存器16-23
因此,$s0表示寄存器16,$s1表示寄存器17,$s2表示寄存器18……$t0表示寄存器8,$t1表示寄存器9
下面将介绍32个寄存器中其余寄存器的映射
MIPS字段
为了使讨论变得简单,给MIPS字段命名如下:
MIPS指令中各字段名称及含义如下:
- op:指令的基本操作,通常成为操作码(opcode)
- rs:第一个源操作数寄存器
- rt:第二个源操作数寄存器
- rd:用于存放操作结果的目的寄存器
- shamt:位移量
- funct:功能。一般称为功能码(function code),用在指明op字段中操作的特定变式
对于下面的MIPS指令,首先给出其十进制表示形式,接着给出二进制表示形式。
add $t0, $s1, $s2
答案:
本例中第一个字段和最后一个字段(0和32)组合起来告诉MIPS计算机该指令要完成加法运算
第二个字段表示加法的第一个源操作数寄存器号(17= $s1)
第三个字段表示加法的另一个源操作数寄存器号(18=$s2)
第四个字段表示存放运算结果的目的寄存器号(8= $t0)
第五个字段在这条指令中没有用到,故置为0
为避免读写冗长乏味的二进制字符串,16进制表示法变得很流行。16是2的4次幂
本例题转十六进制后
下图是十六进制和二进制的转换表
0 | 0000 | 4 | 0100 | 8 | 1000 | c | 1100 |
---|---|---|---|---|---|---|---|
1 | 0001 | 5 | 0101 | 9 | 1001 | d | 1101 |
2 | 0010 | 6 | 0110 | a | 1010 | e | 1110 |
3 | 0011 | 7 | 0111 | b | 1011 | f | 1111 |
例如: eca8 6420
1110 1100 1010 1000 0110 0100 0010 0000
MIPS I型
之前介绍的格式称为R型(用于寄存器),另外一种指令格式称为I型(用于立即数),立即数和数据传送指令用的就是这种格式。
I型的字段如下所示:
逻辑操作
用于简化对字中若干位进行打包或者拆包的操作,称为逻辑操作。。下图给出了C、Java和MIPS中的逻辑操作
逻辑操作 | C | Java | MIPS |
---|---|---|---|
Shift left左移 | << | << | sll |
Shift right右移 | >> | >>> | srl |
Bitwise AND按位与 | & | & | and, andi |
Bitwise OR按位或 | I | I | or,ori |
Bitwise NOT按位取反 | ~ | ~ | nor |
shift移位 sll srl
第一类逻辑操作称为移位(shift)。它们将一个字里面的所有位都向左或向右移动,并在空出来的位上填充0
例如:假设寄存器$s0中的数据是:0000 0000 0000 0000 0000 0000 0000 1001
一条左移4位的指令执行后,得到的新指是:0000 0000 0000 0000 0000 0000 1001 0000
在MIPS中,逻辑左移:sll
逻辑右移:srl
例:
sll $t2,$s0,4
前面介绍R型指令时没有解释shamt字段,它在移位指令中被用于表示移位量
因此上述指令对应的机器语言是
例:
逻辑左移有个额外的好处,就是左移i位相当于乘以2
的i次方
假设左移4位,就相当于乘以2的4次方(即16)
按位与and
按位与(and):该操作仅当两个操作位均为1时结果才为1,否则为0
例如:
and $t0, $t1, $t2
按位或or
按位或(or):该操作在两个操作位中任意一位为1时结果就为1
例如:
or $t0, $t1, $t2
按位取反 NOR
按位取反:该操作仅有一个操作数,将1变成0,0变成1
在MIPS中,引入了或非NOR指令来替代NOT,即一个操作数是0,那么对另外一个操作数而言,结果就等价于NOT
例如:
nor $t0, $t1, $zero
条件操作
程序语言通常使用if语句描述决策,有时也使用go to语句和标签。
如果相等则分支 beq
该指令表示:如果register1和register2中的数值相等,则转到标签为L1的语句执行
beq register1, register2, L1
如果不相等则分支 bne
该指令表示:如果register1和register2的数值不相等,则转到标签为L1的语句执行
bne register1, register2, L1
无条件分支 j
当遇到这个指令时,程序必须分支
例如:
j Exit
无条件直接跳转到Exit标签
在下面这段代码中,f、g、h、i、j都是变量,设该变量依次对应于从$s0到$s4的寄存器,求这条C语言if语句编译后形成的MIPS代码
if (i==j) f = g+h;
else f = g-h;
下图是MIPS代码执行过程的流程图。
第一个表达式表示i和j是否相等,需要一条beq指令
通常,通过测试分支的相反条件来跳过if语句后面的then部分,代码的效率会更高,所以我们使用bne指令
bne $s3, $s4, Else
下一个赋值语句执行一个单操作,如果所有的操作数都分配给寄存器,那么它只是一条指令:
add $s0, $s1, $s2
然后使用无条件分支指令转Exit标签
j Exit
if语句中else部分的赋值语句也可编译成一条指令。我们只需将标签Else加在这条指令前、标签Exit加在这条指令后面
Else: sub $s0, $s1, $s2
Exit: …
最后本例完整的代码:
bne $s3, $s4, Else
add $s0, $s1, $s2
j Exit
Else: sub $s0, $s1, $s2
Exit: …
小于则置位 slt
该指令在比较两个寄存器内容之后,若第一个寄存器小于第二个寄存器,则将第三个寄存器设置为1,否则设置为0
例如:
slt rd, rs, rt
该指令表示:如果 (rs < rt) 则rd = 1; 否则rd = 0;
与bne结合代码:
slt $t0, $s1, $s2 # if ($s1 < $s2)
bne $t0, $zero, L # branch to L
立即数版本的小于则置位slti
在比较中常常使用常数操作数,所以有立即数版本的小于则置位指令。
例如,为了测试寄存器$s2的值是否小于常数10,可以使用如下指令:
slti $t0, $t2, 10
该指令表示,如果s2小于10则t0=1,否则t0等于0
跳转和链接指令 jal
跳转到某个地址的同时将下一条指令的地址保存在寄存器$ra中
格式为:
jal ProcedureLabel
指令中的链接部分表示指向调用点的地址或链接,以允许国产返回到合适的地址。存在在寄存器$ra(31号寄存器)中的链接部分称为返回地址
寄存器跳转 jr
用于case语句,表示无条件跳转到寄存器所指定的地址:
jr $ra
寄存器跳转指令跳转到存储在$ra寄存器中的地址
因此,调用程序或成为调用者,将参数值放在$a0-$a3,然后使用jal x跳转到国产x
被调用者执行运算,将结果放在$v0和$v1,然后使用jr $ra指令将控制返回给调用者
循环
例题:
下面是用C语言编写的传统循环程序;
while (save[i] == k)
i += 1;
假设i和k存放在寄存器$s3 和 $s5中,数组save的基址存放在寄存器$s6中。求这段C程序对应的MIPS汇编代码
解:
第一步需要将save[i] 读入一个临时寄存器中。在读入之前,需要计算它的地址
在将i加到save数组基址以形成访存地址前,由于系统按照字节寻址的缘故,先要将i乘以4;这里我们可以使用逻辑左移指令实现这一乘法,因为左移2位等价于乘4
Loop: sll $t1, $s3, 2
为了得到save[i]的地址,需要将$t1和$s6中save的基址相加
add $t1, $t1, $s6
现在可用该地址将save[i]读入一个临时寄存器中
lw $t0, 0($t1)
下一条指令执行循环判断,如果save[i]≠k则退出循环
bne $t0, $s5, Exit
再下一条指令将i加1
addi $s3, $s3, 1
在循环的末尾,程序跳转到循环开始。随后增加了一个Exit标签
j Loop
Exit: …
完整的代码如下:
Loop: sll $t1, $s3, 2
add $t1, $t1, $s6
lw $t0, 0($t1)
bne $t0, $s5, Exit
addi $s3, $s3, 1
j Loop
Exit: …