文章目录
第二章 指令:计算机的语言
2.1 引言
- 计算机语言:
- 指令:基本单词
- 指令集 Instruction Set:全部指令
- 存储程序概念
2.2 计算机硬件的操作
-
算术运算:
add a, b, c # a = b + c sub d, a, e # d = a - e
-
硬件设计第一条原则:简单源于规整。操作个数可变将给硬件设计带来更大的复杂性。
例题 \color{White}\colorbox{Fuchsia}{例题} 例题:
f = (g + h) - (i + j)
add t0, g, h add t1, i, j sub f, t0, t1
2.3 计算机硬件的操作数
-
寄存器:由硬件构成,数量有限
- 计算机硬件设计的基本元素
- MIPS体系结构中:32个大小为32位(字 word,32位一组)的寄存器。MIPS指令的三个操作数必须从32个寄存器中选取。
- 变量对应的寄存器:
$s0, $s1,...
- 临时寄存器:
$t0, $t1,...
-
硬件设计第二条原则:越小越快。操作个数可变将给硬件设计带来更大的复杂性。
2.3.1 存储器操作数
-
数据传送指令:存储器和寄存器之间移动数据的命令。访问存储器(下标从0开始的一维数组)地址。
大量数据结构(数组和结构)无法存放在寄存器中,只能存放在存储器中。于是MIPS指令需要在存储器和寄存器之间传送数据。
-
取数指令:lw (load word)——将数据从存储器复制到寄存器
-
格式:操作码 + 目标寄存器 + 常数和寄存器(访问存储器)
其中,常数+第二个寄存器=存储器地址
常数:偏移量(offset),基址(base address)寄存器(base register)
例题 \color{White}\colorbox{Fuchsia}{例题} 例题:数组A的起始地址为
$s3
,g:$s1,h:$s2
,表示g = h + A[8]
lw $t0, 32($s3) #将A数组存储器中操作数传送到寄存器,每个内存单元占4位,8*4=32 add $s1, $s2, $t0
字节寻址:一个字的地址必和其包括的4字节中某个地址相匹配,所以连续字的地址相差4。
对齐限制 alignment restriction:数据地址与存储器的自然边界对齐的要求
-
-
存数指令:sw (store word)——将数据从寄存器复制到存储器
例题 \color{White}\colorbox{Fuchsia}{例题} 例题:数组A的起始地址为
$s3
,h:$s2
,表示A[12] = h + A[8]
lw $t0, 32($s3) add $t0, $s2, $t0 sw $t0, 48($s3)
-
寄存器溢出 spilling:将不常使用的变量存回到存储器中的过程。寄存器的访问时间,吞吐率都比存储器高优秀许多。
2.3.2 常数或立即数操作数
-
加立即数:addi (add immediate) —— 一个操作数是常数的算术运算指令
代替从存储器中取常数:
lw $t0, const4($s1) #$t0 = const 4
-
立即数:常数操作数。
addi $s3, $s3, 4 #$s3 = $s3 + 4
-
由于MIPS支持负常数,故不需要设置减立即数指令
-
2.4 有符号数和无符号数
-
二进制数位 binary digit / 位 bit:信息的基本组成单元。
-
最低/高有效位 least/most significant bit:在MIPS字中最右/左边的一位
-
溢出 overflow:操作结果不能被最右端的硬件位表示
-
二进制补码 two’s complement:表示 − 2 n − 1 ∼ 2 n − 1 − 1 -2^{n-1}\sim 2^{n-1}-1 −2n−1∼2n−1−1 的 2 n 2^n 2n 个数。
- 补码取反:基于 x + x ˉ = − 1 x+\bar{x}=-1 x+xˉ=−1.
-
无符号数:
-
符号扩展 sign extension:16位的数转成数值相等的32位数。
-
步骤:将最高有效位(最左边——符号位)重复16次放到32位的左半部分,右半部分照抄。
例题 \color{White}\colorbox{Fuchsia}{例题} 例题:将 − 2 10 -2_{10} −210 从 16 16 16 位二进制数转为 32 32 32 位二进制数
2 10 的 16 位 二 进 制 : 0000 0000 0000 0010 2 − 2 10 的 16 位 二 进 制 : 1111 1111 1111 1110 2 − 2 10 的 32 位 二 进 制 : 1111 1111 1111 1111 1111 1111 1111 1110 2 \begin{aligned} 2_{10}的16位二进制&:\text{0000 0000 0000 0010}_2\\ -2_{10}的16位二进制&:\text{1111 1111 1111 1110}_2\\ -2_{10}的32位二进制&:\text{1111 1111 1111 1111 1111 1111 1111 1110}_2 \end{aligned} 210的16位二进制−210的16位二进制−210的32位二进制:0000 0000 0000 00102:1111 1111 1111 11102:1111 1111 1111 1111 1111 1111 1111 11102
-
2.5 计算机中的指令表示
-
MIPS中通用寄存器 General-Purpose registers 位置:
寄存器映射编号 名称 用途 0 $zero
地址、内容都是0,不能被改写 1 $at
给汇编提供的寄存器(不是很懂) 2~3 $v0, $v1
函数(子程序)出口参数(返回值) 4~7 $a0 ~ $a3
函数(子程序)入口参数 8~15, 24~25 $t0 ~ $t7,$t8,$t9
临时寄存器,不受保护(使用后不必恢复原样) 16~23 $s0 ~ $s7
受保护,调用的子程序结束后,必须恢复原样 26~27 $k0, $k1
操作系统专用寄存器 28 $gp
保存全局变量的地址 29 $sp
栈顶指针 30 $fp
帧指针 31 $ra
子程序返回地址 return address(返回的指令的地址) -
指令格式 instruction format:二进制数字段组成的指令表示形式
<op><rs><rt><rd><shamt><funct>
- R R R指令: 6 6 6 位(表示操作) + 5 5 5 位(源地址一) + 5 5 5 位(源地址二) + 5 5 5 位(目标寄存器) + 5 5 5 位(位移量) + 6 6 6 位(功能码) = 32 32 32位
- MIPS指令占 32 32 32位
- 机器语言:指令的数字形式,指令序列称为机器码
-
MIPS字段 field:
-
R型指令:用于寄存器
- op (opcode):操作码。指令中用来表示操作和格式的字段
- rs (rigister source):第一个源操作数寄存器
- rt (rigister target):第二个源操作数寄存器
- rd (rigister destination):存放操作结果的目的寄存器
- shamt (shift amount):位移量。
- funct (function code):功能码。用于指明 o p op op 字段中操作的特定变式
-
I型指令:用于立即数或数据传送
- 取字指令中:rt ——接受结果的目的寄存器
-
J型指令:跳转地址 target address
-
对应机器码:
例题 \color{White}\colorbox{Fuchsia}{例题} 例题:将汇编语言翻译成机器语言,数组 A A A 的起始地址为
$t1
,h:$s2
,表示A[300] = h + A[300]
lw $t0, 1200($t1) #35 9 8 1200 #op = 0x23 add $t0, $s2, $t0 #8 18 8 8 0 32 sw $t0, 1200($t1) #43 9 8 1200 #op = 0x2b
-
-
硬件设计第三条原则:优秀的设计需要适宜的折中方案。既希望所有指令长度相同,又希望具有统一指令格式,于是分出三种指令格式 R, I, J
2.6 逻辑指令等 Logic Instructions
伪指令 pseudo instruction
-
逻辑位移 shift:(R-型)
sll $t1, $s0, 4 #$t1 = $s0 << 4 bits = $s0 * 16 #0 0 16 9 4 0 #op = 0,funct = 0 srl $t1, $s0, 4 #$t1 = $s0 >> 4 bits #op = 0,funct = 3
- sll:中 o p op op 字段和 f u n c t funct funct 字段都是 0 0 0, s h a m t shamt shamt 代表位移量
- 算数位移(扩展指令):
sal
算数左移,sra
算术右移(intel指令集)- 逻辑右移和算术右移的区别在于,算术右移高位补充符号位,逻辑右移高位补 0 0 0.
- 伪指令:
ror
循环右移,rol
循环左移;rcl
加进位的循环左移,rcr
加进位的循环右移。
-
按位与 AND:(R-型)
and $t0, $t1, $t2 #op = 0,funct = 0x24 andi #立即数与 #op = 0xc
- 掩码 mask:某一位,第二操作数上为 0,按位与可将第一操作数置为 0,“隐藏”某一位。
-
按位或 OR:(R-型)
or $t0, $t1, $t2 #op = 0,funct = 0x25 ori #立即数或 #op = 0xd
andi
和ori
等 I I I 类型指令,会首先用零扩展补充立即数的高16位
-
或非 NOR:(R-型)
nor $t0, $t1, $t2 #$t0 = ~ ($t1 | $t2) #op = 0,funct = 0x27
- 按位取反 NOT:
a NOR 0 = NOT(a)
非指令 NOT 不属于经典指令集,利用或非的一个操作数为 0 0 0 表示
- 按位取反 NOT:
2.7 决策指令
2.7.1 分支
-
条件分支指令 Conditional Branch:
-
beq (branch if equal):(I-型)如果相等则分支
beq reg1, reg2, L1 #op = 4
:若 r e g 1 reg1 reg1 和 r e g 2 reg2 reg2 数值相等则转到 L1 语句
-
bne (branch if not equal):(I-型)如果不相等则分支
bne reg1, reg2, L1 #op = 5
:若 r e g 1 reg1 reg1 和 r e g 2 reg2 reg2 数值不相等则转到 L1 语句
以上属于 I 类型指令,其中 L1 会被转换成立即数:算出和当前指令的下一条指令个数,并乘以4.
目标地址 = PC + 4 + sign-extend(16-bit immediate << 2)
例题 \color{White}\colorbox{Fuchsia}{例题} 例题: f , g , h , i , j f,g,h,i,j f,g,h,i,j 变量依次对应
$s0 - $s4
的寄存器,用MIPS编码表示if(i == j) f = g + h; else f = g - h
bne $s3, $s4, Else add $s0, $s1, $s2 j Exit Else: sub $s0, $s1, $s2 Exit:
-
-
无条件分支指令:
- j (unconditional branch):(J-型)jump
j L1 #op = 2
:必须分支
2.7.2 循环指令
-
循环:
-
直接用标签 Loop 代表循环体
例题 \color{White}\colorbox{Fuchsia}{例题} 例题:变量
i:$s3,k:$s5
,数组 s a v e save save 的基址在$s6
,用MIPS编码表示while(save[i] == k) i += 1;
Loop: sll $t1, $s3, 2 # $t1 = i * 4 add $t1, $s6, $t1 # $t1 = save[i]的地址 lw $t0, 0($t1) # $t0 = save[i] bne $t0, $s5, Exit addi $s3, $s3, 1 j Loop Exit:
-
基本块 basic block:没有分支和分支目标/分支标签的指令序列。
-
slt (set on less than):(R-型)(有符号数)小于则置位
slt $t0, $s3, $s4 # $t0 = $s3 < $s4 ? 1 : 0 #op = 0,funct = 0x2a
由于过于复杂,会延长时钟周期时间,没有小于则分支指令
-
slti (set on less than immediate):(I-型)(有符号数)立即数版本
slti $t0, $s2, 10 # $t0 = $s2 < 10 ? 1 : 0
-
sltu/sltiu (set on less than (immediate) unsigned):无符号整数版本
例题 \color{White}\colorbox{Fuchsia}{例题} 例题:
$s0:0xffff ffff, $s1:0x0000 0001
,则执行slt $t0, $s0, $s1
和sltu $t1, $s0, $s1
后$t0, $t1
的值解:
$t0=1, $t1=0
-
2.7.3 case/switch 语句
- case/switch语句:
- 转移地址表 jump table:又称转移表,指包含不同指令序列地址的表
- jr (jump register):寄存器跳转指令
2.8 计算机硬件对过程的支持
-
过程:根据提供的参数执行一定任务的存储的子程序
-
子程序调用 Procedure call and return:
-
jal (jump-and-link instruction) 跳转和链接指令:(J-型)跳转到某个地址的同时将下一条指令的地址保存在寄存器 $$ra$ 中
jal ProcedureAddress #op = 3
-
链接部分:指向调用点的地址或链接
-
返回地址 return address:存储在寄存器 $$ra$ 中的链接部分
-
程序计数器 PC(program counter):一个保存当前运行的指令地址的寄存器,即指令地址寄存器。
jal
一般将 P C + 4 PC+4 PC+4 保存在寄存器 $$ra$ 中,即jal $ra
-
-
2.8.1 堆栈
目的:由于寄存器在调用后需恢复到过程调用前所存储的值,所以需要位置保存寄存器旧值,以便于恢复。
-
栈:后进先出的数据结构,用于寄存器换出
- 栈指针 stack pointer:指示栈中最近分配的地址的值,指示寄存器被换出的位置,或寄存器旧值的存放位置
- 以字为单位进行调整
- MIPS中是第 29 29 29 号寄存器 $$sp$。
- 入栈时栈指针减小( − 4 -4 −4):因为栈“增长”是按照地址从高到底的顺序。
例题 \color{White}\colorbox{Fuchsia}{例题} 例题:将C语言编译为MIPS汇编语言,参数变量 g , h , i , j , f g,h,i,j,f g,h,i,j,f 分别对应 $$a0,$a1,$a2,$a3,$s0$
int leaf_example(int g, int h, int i, int j){ int f; f = (g + h) - (i + j); return f; }
解:
leaf_example: #保存过程中使用的寄存器 addi $sp, $sp, -12 #建立三个字的空间 sw $t1, 8($sp) #存入数据 sw $t0, 4($sp) sw $s0, 0($sp) #过程实体 add $t0, $a0, $a1 add $t1, $a2, $a3 sub $s0, $t0, $t1 #将f的值复制到返回值寄存器v0 add $v0, $s0, $zero #弹出数据,恢复是寄存器旧值 lw $s0, 0($sp) lw $t0, 4($sp) lw $t1, 8($sp) addi $sp, $sp, 12 #返回地址跳转 jr $ra
- 栈指针 stack pointer:指示栈中最近分配的地址的值,指示寄存器被换出的位置,或寄存器旧值的存放位置
2.8.2 嵌套过程
-
叶过程 leaf procedure:不调用其他过程的过程
-
调用非叶过程:寄存器在嵌套保存的地址或内容上会导致冲突。
-
解决方法:将所有必须保留的寄存器都压栈。
例题 \color{White}\colorbox{Fuchsia}{例题} 例题:将C语言编译为MIPS汇编语言,参数变量 n n n 分别对应
$a0
int fact(int n){ if(n < 1) return 1; else return (n * fact(n - 1)); }
解:
fact: #保存返回地址寄存器和变量 n 寄存器 addi $sp, $sp, -8 sw $ra, 4($sp) sw $a0, 0($sp) #判断是否跳转 slti $t0, $a0, 1 beq $t0, $zero, L1 # n < 1时 addi $v0, $zero, 1 #由于旧值未改变,直接改变栈指针即可 addi $sp, $sp, 8 #返回地址跳转 jr $ra L1: # n >= 1时 addi $a0, $a0, -1 jal fact #弹出数据,恢复栈指针,旧寄存器返回地址和旧参数 lw $a0, 0($sp) lw $ra, 4($sp) addi $sp, $sp, 8 #值寄存器v0 = 旧参数a0 * 当前值寄存器v0 mul $v0, $a0, $v0 #返回地址跳转 jr $ra
-
2.10 MIPS中32位立即数和寻址
2.10.1 32位立即数
伪指令为
li $s0 0x3f3f2f2f
-
lui (load upper immediate):(I-型)读取立即数高位指令
lui $t0(reg), 255(const) #op = 0xf
- 将立即数 c o n s t const const 存入寄存器常数的高 16 16 16 位,低位用 0 0 0 补充
-
32 32 32 位的立即数装入寄存器中:
MIPS实现:
- 先用 lui (load upper immediate) 装入高16位,且低位用0补充
lui $s0 0x3f3f
. - 在用 ori 填补低16位
ori $s0 $s0 0x2f2f
.
例题 \color{White}\colorbox{Fuchsia}{例题} 例题:加载 32 32 32 位常量 0000 0000 0011 1101 0000 1001 0000 0000 \text{0000 0000 0011 1101 0000 1001 0000 0000} 0000 0000 0011 1101 0000 1001 0000 0000 到寄存器 $$s0$ 的MIPS代码
lui $s0, 16 # 0000 0000 0011 1101 = 16 ori $s0, 2304 # 0000 1001 0000 0000 = 2304
- 先用 lui (load upper immediate) 装入高16位,且低位用0补充
2.10.2 分支和跳转中的寻址
32 32 32 位的地址装入寄存器中:伪指令为
la $t0 0x10010008
(比如将内存地址装载到寄存器中)
diate)** 装入高16位,且低位用0补充 lui $s0 0x3f3f
.
2. 在用 ori 填补低16位 ori $s0 $s0 0x2f2f
.