11111 00001)分别表示34条单地址指令。
第 3 版 · OCR 辅助 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照
<!-- page: 120 -->
指令系统
4.3.1MIPS汇编语言和机器语言
机器语言程序是一个由若干条指令组成的序列。从前面对指令格式的介绍可以知道,
每条指令由若干字段组成,每个字段都是一串由0、1组成的二进制数字序列。因此,程序员
要读懂一个机器语言程序很费劲,也很难用机器语言直接编写程序。
为了能直观地表示机器语言程序,引入了一种与机器语言一一对应的符号化表示语言,
称为汇编语言。汇编语言中,用容易记忆的英文单词或缩写来表示指令操作码的含义,用标
号、变量名称、寄存器名称、常数等表示操作数或地址码。这些英文单词或其缩写、标号、变
量名称等都被称为助记符。以下简要介绍32位MIPS指令系统和 MIPS汇编语言。
1.MIPS 指令中数据的表示
MIPS提供了32个32位通用寄存器,寄存器编号占5位,各寄存器的名称、编号和功
能见表4.2,
表 4.2 MIPS 通用寄存器
名 称
编号
功 能
zero
0
恒为0
at
1
为汇编程序保留
V0yl
2~3
过程调用返回值
a0~a3
4~7
过程调用参数
t0~t7
8~15
临时变量,在被调用过程无须保存
s0~s7
16~23
在被调用过程需保存
t8~19
24~25
临时变量,在被调用过程无须保存
k0~kl
26~27
为 OS 保留
BP
28
全局指针
sp
29
栈指针
fp
30
帧指针
ra
31
过程调用返回地址
寄存器的汇编表示以$符号开始,可以使用名称(如$a0),也可以使用编号(如$4)。
MIPS还提供了32个32位的单精度浮点寄存器,用汇编符号$f0~$f31表示。它们
可配对成16个64位浮点寄存器,用来表示64位双精度浮点数。
另外,MIPS中提供了两个乘商寄存器 Hi和 Lo,它们无须程序员在指令中显式给出。
用32位的Hi和Lo可实现64位寄存器。在执行乘法运算时,Hi和Lo联合用来存放64位
乘积,而在执行除法运算时,最终的余数存放在 Hi中,商在Lo中。
MIPS 中用程序计数器 PC指出下条指令的地址。
MIPS的存储器按字节编址。对于存储器数据,其操作数有效地址为32位,通过一个
32位寄存器的内容加16位偏移量得到,16位偏移量是带符号整数,故可访问的地址空间大
小为22字节。采用大端方式(big endian)存放数据,数据要求按字边界对齐。只能通过
第4章
103
<!-- page: 121 -->
计算机组成与系统结构(第3版)
104
load/store 指令访问存储器数据。
对于立即操作数,指令中给出的位数为16位,指令执行时,需要将其进行符号扩展或0
扩展,变成32 位操作数后才能参加运算。
2.MIPS 指令格式和寻址方式
MIPS 是典型的RISC处理器,采用32 位定长指令字,操作码字段也是固定长度,没有
专门的寻址方式字段,由指令格式确定各操作数的寻址方式。
指令格式只有3种,如图4.5所示。
31
2625
2120
1615
1110
65
0
OP
rs
rt
rd
shamt
fune
31
2625
(a)R-型指令
2120
1615
0
OP
rs
rt
立即数
31
2625
(b) 1-型指令
0
OP
直接地址
(c)」-型指令
图4.5 MIPS指令格式
R-型指令属于RR型指令,即操作数和结果都存放在寄存器中,这里R表示寄存器
(register)。其操作码OP为000000,操作类型由func字段指定,若是双目运算类指令,则
rs和rt的内容分别作为第一和第二源操作数,结果送rd;若是移位指令,则对rt的内容进
行移位,结果送rd,所移位数由shamt字段给出。因为一条指令需要左移或右移若干位,所
以 MIPS 中移位指令多用桶形移位器实现以提高速度。R-型指令的寻址方式只有一种,就
是寄存器寻址。
I-型指令是立即数型指令,这里1表示立即数(immediate operand)。若是双目运算类
指令,则将rs的内容和立即数分别作为第一和第二源操作数,结果送 rt;若是 load/store 指
令,则将 rs的内容和立即数符号扩展后的内容相加作为内存单元地址,load 指令将内存单
元内容送rt,store指令将rt内容送内存单元;若是条件转移(分支)指令,则对rs和rt内容
进行指定的运算,根据运算的结果,决定是否转到转移目标地址处执行,转移目标地址通过
相对寻址方式得到,即将PC的内容和立即数符号扩展后的内容相加得到。由此可知,I型
指令的寻址方式有4种,就是寄存器寻址、立即数寻址、相对寻址、基址或变址寻址。
J-型指令主要是无条件跳转指令,这里J表示跳转(jump)。指令中给出的是26位直接
地址,只要将当前PC的高4位拼上26位直接地址,最后添两个0就可以得到32位的跳转
目标地址。J-型指令的寻址方式只有一种,就是变通的直接寻址。
例4.2 为什么」型指令中的跳转目标地址最后两位要添0?如何实现该功能?
解:因为MIPS机器采用32位定长指令字,其存储单元采用字节编址,所以一条指令
占4个存储单元,因而指令地址总是4的倍数,即地址最后两位总是0,无须在指令中显式
给出,只要在实现指令功能的数据通路中具有添加00的电路即可。
3.MIPS 汇编语言
表4.3和表4.4分别是MIPS汇编语言和机器代码示例列表。表4.3中列出了常用的5
<!-- page: 122 -->
指令系统
类指令:算术运算、存储访问、逻辑运算、条件分支、无条件跳转。每类中给出最具代表性的
指令的名称、汇编形式示例、含义和文字说明。表4.4中给出了常用指令的机器代码示例,
分别包括操作码汇编助记符、指令格式类型、指令各字段的十进制值和对应的汇编表示。
表 4.3 MIPS 汇编语言示例列表
类别
指 令 名 称
汇 编 举 例
含 义
备 注
算术
add
add $s1,$s2,$s3
$s1=$s2+$s3
三个寄存器操作数
运算subtract
sub $s1,$s2,Ss3
$s1=$s2-$s3
三个寄存器操作数
存储load word
lw $s1,100($s2)
$s1=Memory[$s2+100]
从内存取一个字到寄存器
访问store word
sw $s1,100($s2)
Memory[$s2+100]=$sl
从寄存器存一个字到内存
and
and $s1,$s2,Ss3
$s1=$s2 &.$s3
三个寄存器操作数,按位与
or
or $s1,$s2,$s3
$s1=$s2|Ss3
三个寄存器操作数,按位或
逻辑
运算
nor
nor Ss1.Ss2.Ss3
$s1=~($s2|$s3)
三个寄存器操作数,按位或非
and immediate
andi $s1,$s2,100
$s1=$s2 &.100
寄存器和常数,按位与
or immediate
ori $s1,$s2,100
$s1=$s2|100
寄存器和常数,按位或
shift left logical
sll $s1,$s2,10
$s1=$s2<10
按常数对寄存器逻辑左移
shift right logical
srl $s1,$s2,10
$s1=$s2>10
按常数对寄存器逻辑右移
branch on equal
beq $s1,$s2,L
if( $s1==$s2)go to L
相等则转移
条件
分支
branch on not
equal
bne $s1,$s2.L.
if( $s1!=$s2)go to L
不相等则转移
set on less than
slt Ss1,$s2,$s3
if( $s2<$s3)$s1=1;
else $s1=0
小于则置寄存器为1,否则
为0,用于后续指令判0
set on less
than immediate
slt Ss1,$s2,100
if($s2<100)$s1=1;
else $s1=0
小于常数则置寄存器为1,
否则为0,用于后续指令判0
无条
件跳
转
jump
j L
go to L
直接跳转至目标地址
jump register
jr $ra
go to $ra
过程返回
jump and link
jal L
Sra=PC+4;go to L
过程调用
表 4.4 MIPS机器代码示例列表
指 令
格式
指 令 举 例
备 注
add
R
0
18
19
17
0
32
add
$s1,$s2,$s3
sub
R
0
18
19
17
0
34
sub $s1,$s2,$s3
lw
I
35
18
17
100
lw $s1,100($s2)
sw
1
43
18
17
100
sw
$s1,100($s2)
and
R
0
18
19
17
0
36
and $s1,$s2,$s3
第4章
105
or
R
0
18
19
17
0
37
or $s1,$s2,$s3
<!-- page: 123 -->
计算机组成与系统结构(第 3版)
续表
指 令
格式
指 令 举 例
备 注
106
nor
R
0
18
19
17
0
39
nor
$s1,$s2,$s3
andi
1
12
18
17
100
andi $s1,$s2.100
ori
1
13
18
17
100
ori
$s1,$s2,100
sll
R
0
0
18
17
10
0
sll
$s1,$s2,10
srl
R
0
0
18
17
10
2
srl $s1,$s2,10
beq
1
4
17
18
25
beq
$s1,$s2,100
bne
1
5
17
18
25
bne
$s1,$s2,100
slt
R
0
18
19
17
0
42
slt $s1,$s2,$s3
j
J
2
2500
j10000
jr
R
0
31
0
0
0
8
jr $ra
jal
J
3
2500
jal 10000
字段大小
6位
5位
5位
5位
5位
### 6 位
R-型指令
R
OP
rs
rt
rd
shamt
func
1-型指令
1
OP
rs
rt
address
从这两个表中可明显看出机器代码和汇编表示的一一对应关系。根据机器代码和汇编
表示之间的对应表(称为指令解码表),可以很容易地实现两者的转换。从汇编表示转换为
机器代码的过程称为汇编,从机器代码转换为汇编表示的过程称为反汇编。
例4.3 若从 MIPS指令机器代码与汇编表示对应表中查出操作码(OP字段)000000
对应R-型指令,又从R-型指令解码表中查到功能码(func字段)100000对应 add指令。回
答下列问题。
(1)汇编表示“add $t0,$s1,$s2”对应的MIPS指令的机器代码是什么?
(2)假定一条MIPS指令的二进制机器代码表示为0000 0000 1010 1111 1000 0000 0010 0000,
则该指令对应的MIPS 汇编表示形式是什么?
解:汇编和反汇编过程依赖于解码表进行,只要通过查表就可实现指令的机器代码和
汇编表示之间的转换。
(1)由表4.4可知,add指令是R-型指令,对应的OP字段为0,即二进制000000,func
字段为32,即二进制100000,shamt字段为0;由表4.2可知,$t0、$s1和$s2的编号分别
为8、17和18。因此,得到该指令各字段的值如图4.6所示。
31
2625
2120
1615
1110
65
0
000000
10001
10010
01000
00000
100000
OP
rs
rt
rd
shamt
func
图4.6 指令各字段分解
即汇编表示“add $t0,$s1,$s2”对应的指令机器代码是0000 0010 0011 0010 0100
<!-- page: 124 -->
指令系统
#### 0000.0010 0000,
(2)指令的前6位操作码为000000,是一条R-型指令,按照R-型指令的格式,指令分解
为如图4.7所示的6个字段,从而得到rs=00101,rt=01111,rd=10000,shamt=00000,
func=100000。
31
2625
2120
1615
1110
65
0
000000
00101
01111
10000
00000
100000
OP
rs
r
rd
shamt
fune
图4.7 指令各字段分解
由表4.4可知是add操作。rs、rt、rd的十进制值分别为5、15、16,从表4.2可知,rs、rt
和rd分别为$al、$t7和$s0。故对应汇编形式为“add $s0,$al,$t7”或“add $16,
$5,$15”。
4.3.2选择结构的机器代码表示
选择结构根据判定条件来控制一些语句是否被执行。对应高级语言中的选择语句,在
机器语言中提供了各种条件码(标志位)的设置功能以及各种分支(条件转移)指令和无条件
转移指令。编译器通过条件码设置指令和各类转移指令来实现选择结构语句。
例4.4 假定C语言赋值语句“f=(g+h)—(i+j);”中变量i、j、f、g、h由编译器分别
分配给 MIPS寄存器$t0~$t4。要求给出编译后的 MIPS机器代码和汇编表示。
解:只要用3条R-型指令即可,其中,有两条是add指令,有一条是sub指令。3条指
令的OP字段都为000000,根据表4.4可知,add指令func字段为32=100000B,sub 指令
func字段为34=100010B。从表4.2可知,寄存器$t0~$t7 对应8~15。因此该赋值语句
对应的 MIPS机器代码和汇编表示(#后为注释)如下。
### 000000 01011 01100 01101 00000 100000
add st5, st3,St4 #g+h
### 000000 01000 01001 01110 00000 100000
add $t6, st0, St1 #i+j
### 000000 01101 01110 01010 00000 100010
sub $t2,9t5,St6
#f=(g+h)-(i+j)
例4.5 对于C程序段“if(i==j)f=g+h;elsef=g-h;”,假定i、j、f、g、h由编译器
分别分配给 MIPS寄存器$s1~$s5。要求给出编译后的 MIPS 汇编表示。
解:首先要有一条分支指令能根据i、j是否相等进行转移,可选用表4.3中beq或bne
指令。此外,还要有一条无条件转移指令,可选用表4.3中的j指令。上述程序段对应的
MIPS 汇编表示如下:
bne$s1, $s2, else
add Ss3,5s4,Ss5
j exit
else: sub $s3, Ss4, $s5
exit:
#if(1≠j),jump to else
#f=g+h
#jump to exit
#f=g-h
例4.6 对于C程序段“if(i<j)f=g+h;else f=g-h;”,假定i、j、f、g、h由编译器分
别分配给 MIPS寄存器$s1~$s5。要求给出编译后的 MIPS汇编表示。
解:首先要有一条比较i、j大小的指令,该指令能根据比较结果设置标志位,然后分支
第4章
107
<!-- page: 125 -->
计算机组成与系统结构(第3版)
108
指令根据标志位的值进行转移,因此,应选用表4.3中的slt指令、beq或 bne 指令。在比较
标志位的值时,需要判断是否为0,此时,用0号寄存器$zero表示0。上述程序段对应的
MIPS 汇编表示如下:
slt $s6,$s1,Ss2
beq $s6,9zero, else
add Ss3,$s4,Ss5
j
exit
Ss3,Ss4,Ss
else: sub
exit:
#if(i<j)$s6=1 else 5s6=0
#ifSs6=0,jump to else
#f=g+h
#jump to exit
#f=g-h
4.3.3循环结构的机器代码表示
循环结构是指可重复执行的一组语句,如 while 语句、for 语句等。分支指令在循环结
构中起重要作用,主要用来判断循环结束条件是否满足。此外,大多数循环体内需要对数组
元素进行处理,因此,需要用到自动变址寻址,如果指令系统不提供自动变址,则编译器需要
选用对变址寄存器进行增量的指令来使每次循环能按顺序取不同的数组元素。
例4.7以下是一个C程序段:
while(i!=k)(
x=x+A[i];
i=i+1;
)
假定x、i、k由编译器分别分配给 MIPS寄存器$s1,$s2,$s3,数组A的每个元素
为一个 32位,首地址存放在$s5中,要求给出编译后的MIPS汇编表示。
解:循环体内有一个数组元素的访问,首先要计算每次循环中数组元素A[i]的地址,
它应等于 A的首地址加上偏移量。因为数组元素是一个32位的字,而MIPS按字节编址,
所以偏移量等于i×4,可以用乘法指令实现,也可以用加法指令(两次加倍)或移位指令(左
移2位)来实现×4。从第3章介绍的运算算法来看,乘法指令所需的时间最长,所以一般不
用乘法指令实现×4操作。从内存读取数组元素的功能用lw指令实现。
循环开始时,先用分支指令beq判断循环结束条件,以便在循环结束条件满足时跳出循
环体。循环结束后的第一条指令用一个标号 Exit 标识;循环最后要有一条无条件转移指令
j,以转到循环体的开始,循环体内第一条指令的标号为Loop。
MIPS 没有自动变址寻址方式,因此用一条显式的加法指令 addi实现数组下标增量。
编译后的 MIPS 汇编表示如下:
Loop: beq
s11
add
1w
add
addi
j
$s3,$s2, Exit
Ss7,Ss2,2
$s7,Ss7,ss5
$s6, 0(5s7)
Ss1,$s1, Ss6
Ss2,$s2, 1
Loop
#i×4
#§7= A[i]的地址
#§6=A[i]
#x=x+A[i]
#1+1
Exit:
<!-- page: 126 -->
指令系统
4.3.4过程调用的机器代码表示
子程序的使用有助于提高程序的可读性,并有利于代码重用,它是程序员进行模块化编
程的重要手段。子程序的使用主要是通过过程或函数调用实现,为叙述方便,本书将过程
(调用)和函数(调用)统称为过程(调用)。过程允许程序员使用参数将过程与其他程序和数
据分离,调用过程只要传送输入参数给被调用过程,最后再由被调用过程返回结果给调用过
程即可。为了彼此统一,并能配合操作系统工作,编译的过程代码之间必须遵循一定的调用
接口约定,编译器和汇编程序员强制按照这些约定生成或编写机器级代码,包括寄存器的使
用、栈帧的建立和参数传递等。
1.MIPS 中用于过程调用的指令
调用指令是一种无条件转移指令,在MIPS中称为跳转并链接(jump and link)指令,指
令名称为jal,采用J型格式,具有两个功能:①保存下条指令地址(即返回地址)到31号寄
存器;②跳转到指定地址处执行。其汇编形式和指令格式分别参见表4.3和表 4.4。例如,
指令jal10000的功能为“$31=PC+4;go to 10000”
返回指令也是一种无条件转移指令,在MIPS中称为寄存器跳转(jump register)指令,
指令名称为jr,采用R-型格式,其功能为跳转到寄存器指定的地址处执行。其汇编形式和
指令格式分别参见表4.3和表4.4。例如,指令jr $31的功能为转到调用程序的返回地址
(在$31中保存)处执行,jr指令中的寄存器也可以是除$31以外的其他寄存器,所以,jr指
令也可用于 switch case 语句中的跳转执行。
2.过程调用时 MIPS 寄存器的使用约定
假定过程P调用过程Q,则过程调用的执行步骤如下。
(1)P将入口参数放到Q能访问到的地方。
(2)P将返回地址存到特定的地方,然后将控制转移到Q。
(3)Q为P保存现场,并为自己的局部变量分配空间。
(4)执行过程Q。
(5)Q将返回结果放到P能访问到的地方。
(6)Q取出返回地址,将控制转移到P。
从上述执行步骤来看,在过程调用中,需要为入口参数、返回地址、调用过程执行时用到
的寄存器、被调用过程中的局部变量、过程返回时的结果等数据找到存放空间。如果有足够
的寄存器,最好把这些数据都保存在寄存器中,这样,CPU执行指令时,可以快速地从寄存
器取得这些数据进行处理。但是,用户可见寄存器数量有限;并且它们是所有过程共享的,
给定时刻只能被一个过程使用;此外,对于过程中使用的一些局部变量(如数组和结构等复
杂类型数据)也不可能保存在寄存器中。因此,除了寄存器外,还需要有一个专门的存储区
域来保存这些数据,这个存储区域就是栈(stack)。那么,上述数据中哪些存放在寄存器,哪
些存放在栈中呢?寄存器和栈的使用又有哪些规定呢?
尽管硬件对寄存器的用法几乎没有任何规定,但在软件实际使用寄存器时还要遵循一
定的惯例,使程序员、编译器和操作系统等都按照统一的约定处理。
假定过程P调用过程Q,则MIPS程序中对过程调用时寄存器的使用规定如下(参见
第4章
109
表 4.2)。
<!-- page: 127 -->
计算机组成与系统结构(第3版)
110
(1)$a0~$a3 用于传递前4个非浮点数入口参数。因此在过程P中应先将入口参数
送入$a0~$a3,然后调用Q。若入口参数超过4个,则其余参数保存到栈中。
(2)$v0~$v1用于传递从Q返回的非浮点数返回参数。因此在过程Q中应先将返
回参数送入$v0~$v1再返回P。
(3)$ra用于存放返回地址,由调用指令(jal)自动将返回地址送入$ra(即$31)。
(4)$s0~$s7在过程P中原来的值从过程Q返回后可被P继续使用,因此,若在过
程Q中使用这些寄存器,则必须先将其内容保存到栈后才能使用,并在返回P前恢复,因
此,它们被称为保存寄存器。
(5)$t0~$t9的值从过程Q返回后在P中不再需要使用,若需要则由P自己保存,
因此,在过程Q中不需对其内容保存,可以自由使用,所以,它们被称为临时寄存器。
(6)$a0~$a3的值从过程Q返回后在P中也不再需要使用,若需要,也由P自己保
存,因此,过程Q不需要为过程P对其内容进行保存。
3.MIPS 中的栈和栈帧
上文提到,过程调用时的一些数据除了可存放到寄存器外,还有一些数据被存放到栈
中。MIPS中有一个专门的栈指针寄存器$sp,用来指示栈顶元素,栈中每个元素的长度为
32位,没有专门的入栈指令(push)和出栈指令(pop)。入栈、出栈操作分别用sw、lw 指令
实现,因而不能自动进行栈指针调整,需用addi 指令调整$sp的值。
MIPS中,栈从高地址向低地址方向增长,而取数、存数则从低地址向高地址方向进行,
MIPS采用大端方式,每入栈1个字,则$sp-4→$sp,每出栈1个字,则$sp+4→$sp。
例4.8 假定将返回地址$ra和参数$aO保存到栈中,写出其指令序列,并画图说明
$ra和$a0在栈中的位置。
高地址
解:假定栈指针寄存器$sp指向栈顶,返回地
址$ra和参数$a0从栈顶处开始存放,其存放位
过程调用前的Ssp-
Sra
栈增
长的
SaO
置如图 4.8 所示。
过程执行中的 Ssp-
方向
在栈中保存信息的指令序列如下:
低地址
addi
$sp,Ssp,-8
sw
Sra,4($sp)
图4.8 栈中数据的存放
Sa0,0($sp)
sw
每个过程都有自己的栈区,称为栈帧(stack frame),因此,一个栈由若干栈帧组成,每个
栈帧用专门的帧指针寄存器指定起始位置,MIPS中的帧指针寄存器是$fp。当前栈帧范
围在帧指针$fp和栈指针$sp指向区域之间。过程执行时,由于不断有数据入栈,所以栈
指针会动态移动,而帧指针固定不变。对程序来说,用固定的帧指针来访问变量要比用变化
的栈指针方便得多,也不易出错,因此,在一个过程内对栈中信息的访问大多通过帧指针进
行。但是,如果当前过程的栈帧(即当前栈帧)中没有局部变量,则编译器大多不设置和恢复
帧指针,以减少时空开销。当需要使用帧指针$fp时,通常以过程调用时的栈指针$sp或
$sp-4作为其初始值,这样,$fp总是指向当前栈帧前一个字或当前栈帧第一个字的起始
位置。
假定过程P调用过程Q,则在调用过程P中入栈保存的信息称为调用者保存信息,存
放在过程P的栈帧中;在被调用过程Q中入栈保存的信息称为被调用者保存信息,存放在
<!-- page: 128 -->
指令系统
Q的栈帧中。图4.9给出了在过程调用前、调用中和调用后的MIPS用户栈的变化状态。
高地址
临时寄存器
Sto~St9
(必要时)
调用过程
参数寄存器
栈帧
Sa0~Sa3
(必要时)
其余输入参
数(>4个时)
Ssp-
低地址
临时寄存器
StO~St9
(必要时)
参数寄存器
SaO~Sa3
(必要时)
其余输入参
数(>4个时)
高地址
调用过程
栈帧
返回地址 Sra
(嵌套时)
Sfp-
保存寄存器
SsO~Ss7
当前
(存在时)
栈帧
局部数组
和结构等
(存在时)
Ssp-
:
低地址
高地址
临时寄存器
Sto~St9
(必要时)
调用过程
参数寄存器
栈帧
SaO~Sa3
(必要时)
其余输入参
数(>4个时)
Ssp-
低地址
(a)过程调用前
(b)过程调用中
图4.9 过程调用时 MIPS 中的栈和栈帧的变化
(c)过程调用后
如图4.9(a)所示,在调用过程中遇到新的一个过程调用时,调用过程根据需要确定是否
将临时寄存器或参数寄存器保存到自己的栈帧(调用过程栈帧)中,同时,对于浮点数参数和
超过4个的其余非浮点数参数也要保存到自己的栈帧中,然后转入被调用过程。如图4.9
(b)所示,在被调用过程中,需要时需将帧指针$fp设置为$sp,在$fp和$sp指向的区间
之间的是当前栈帧。如果当前过程是非叶子过程(叶子过程指不再调用其他过程的过程),
则返回地址入栈保存;若在过程中用到保存寄存器,则将它们入栈保存;然后根据过程中局
部数组或结构等情况,对局部变量进行入栈保存;若$fp有被破坏的情况(如嵌套调用)发
生,还需将$fp保留到当前栈帧中;如果是递归调用,则所有输入参数都需要入栈保存。被
调用过程执行结束返回前,必须释放局部变量占用的栈区,并恢复保存的各个寄存器,最后
可根据$fp的值恢复进入被调用过程时的栈指针$sp。这样,在回到调用程序后,栈中状
态和过程调用前一样,如图4.9(c)所示。
4. MIPS 过程调用协议
在程序执行过程中,每调用一次过程,都会在栈中生成一个对应的新栈帧,而在执行返
回指令前对应的栈帧在栈中都已被释放。栈帧的生成和释放方式可以有多种,但不管采用
什么方式,调用过程和被调用过程都必须遵循一定的步骤。
以下步骤是大多数 MIPS系统上采用的过程调用协议。
1)调用过程P在过程调用前的执行步骤
(1)若P在返回后还要用到$a0~$a3和$t0~$t9中某些寄存器,则需将这些寄存
器压到当前栈帧。
第4章
111
<!-- page: 129 -->
计算机组成与系统结构(第3版)
112
(2)将前4个参数送到$a0~$a3,其他参数压到当前栈帧。
(3)执行jal指令,该指令将返回地址保存到$ra($31)中,并将控制转移到被调用
过程。
2)被调用过程 Q中的执行步骤
由三段组成:开始段、本体段(过程体)和结尾段。本体段进行具体处理。
开始段主要进行栈帧生成、寄存器保存和局部变量空间申请,其处理步骤如下。
(1)通过调整栈指针$sp来申请栈帧,即将$sp的值减去栈帧大小。
(2)若Q需调用其他过程,则将返回地址寄存器$ra和帧指针$fp压入当前栈帧。
(3)设置帧指针$fp,其值为当前栈指针$sp加栈帧大小。
(4)若Q中用到$s0~$s7中的某些寄存器,则需将这些寄存器压入当前栈帧。
(5)若Q中的局部变量发生寄存器溢出(即寄存器不够分配),则局部变量在Q的栈帧
中分配空间。若有像数组和结构之类的复杂类型局部变量,则在当前栈帧中分配空间。
由此可见,栈帧大小应至少等于上述步骤中用到的存储单元的总和。
结尾段主要进行寄存器恢复、栈帧释放,并返回到调用程序。其处理步骤如下。
(1)若保存了$s?~$s7中某些寄存器值,则将这些值从当前栈帧中恢复到寄存器。
(2)若保存了返回地址和帧指针,则将它们分别恢复到寄存器$ra和$fp中。
(3)调整栈指针$sp以释放栈帧,即将$sp的值加栈帧大小,或将$fp的值送$sp。
(4)用返回指令jr $ra将控制权返还给调用过程。
例4.9 写出以下C语言过程对应的 MIPS汇编表示。
void swap(int v[], int k)
int temp;
temp=v[k];
v[k]=v[k+1];
y[k+1]=tempi
解:swap不是主程序(main函数),是一个被调用过程,但它不再调用其他过程,所以
是个叶子过程。
按照调用协议,swap过程的调用过程已将参数v和k分别放在参数寄存器$a0和
$al中。参数v是一个数组的首地址。假定在swap过程体中先使用临时寄存器$t0~
$t9,不够时再使用保存寄存器$s0~$s7,局部变量 temp分配在寄存器$t0中。如果临
时寄存器够用的话,则不需要在栈帧中保存调用过程的现场,即不需将$s0~$s7的值保
存在栈帧中。
按照上述 MIPS过程调用协议,开始段无须保存任何寄存器的值,也无须在栈中分配局
部变量。因为swap 是叶子过程,故无须保存返回地址和帧指针,由此可见,swap 对应的栈
帧为空,结尾段直接返回即可。swap的汇编表示如下:
swap;s1l
add
Iw
1w
$t1,$al,2
St1, St1, 5a0
$t0, 0($t1)
$t2, 4($t1)
#k<<2,multiply k by 4
#address of v[k]
#load v[k]
#load v[k+1]
<!-- page: 130 -->
指令系统
$t2, 0($t1)
Sw
Sw
5t0, 4($t1)
jr s31
#store v[k+1] into v[k]
#store old v[k] into v[k+1]
#return to caller
例4.10 要求写出以下3个C语言函数对应的 MIPS 汇编表示。
void set_array(int num)
(
int i,array[10];
for(i=0;i<10;i++)(
array[i]=compare(num,i);
}
int compare(int a, int b)
(
if(sub(a,b)>=0)
return 1;
else
return 0;
}
int sub(int a, int b)
4
return a-b;
解:为了尽量减少指令条数,并减少访问内存次数。在每个过程的过程体中总是先使
用临时寄存器$t0~$t9,临时寄存器不够或者某个值在调用过程返回后还需要用,就使用
保存寄存器$s0~$s7。
MIPS指令系统中没有寄存器传送指令,为了提高汇编表示的可读性,引入一条伪指令
move 来表示寄存器传送,汇编器将其转换为具有相同功能的机器指令。伪指令“move
$t0,$s?”对应的机器指令为“add $t?,$zero,$s?”。
(1)过程 set_array。入口参数为num,没有返回参数,有一个局部简单变量i和局部数
组,被调用过程为 compare。假定i分配在临时寄存器$t2中,这样无须在栈帧中为i分配
空间,也无须在栈帧中保存$t2。栈帧中除保留所用的保存寄存器外,必须保留返回地址
$ra,是否保存$fp要看具体情况。如果确保后面都不用到$fp,则可以不保存,但为了保
证$fp的值不被后面的过程覆盖,通常情况下,应该保存$fp的值,此外,需要在栈帧中为
局部数组分配4×10=40字节的空间。从过程体来看,从 compare 返回后还需要用到数组
基地址,故将其分配给保存寄存器$s1。因此,$sl需保存在栈帧中,另外加上返回地址、
帧指针和局部数组,其栈帧空间最少为4×3+40=52字节。
过程 set_array 的汇编表示如下:
set-array: addi
Sw
sw
SW
$sp,$sp,-52
Sra,48(Ssp)
$fp,44($sp)
$s1,40($sp)
#generate stack frame
#save Sra on stack
#save Sfp on stack
#save 5s1 on stack
第4章
113
<!-- page: 131 -->
计算机组成与系统结构(第3版)
114
for-loop:
exit1:
addi
move
move
move
slti
beq
move
move
jal
811
add
sw
addi
j
lw
1w
1w
addi
jr
Sfp,$sp,48
Ss1,Ssp
st?,Sa0
St2,$zero
$t1, $t2,10
St1, $zero, exit1
sa0, $t0
Sal, $t2
compare
St1, $t2,2
$t1,ss1,st1
$v0, 0($t1)
St2,$t2,1
for-loop
Sra,48($sp)
$fp,44(Ssp)
$s1, 40($sp)
$sp,$sp,52
Sra
#set $fp
*Ss1=base address of array
#$tO=num
#i=0
#if i<10$tl=1else St1=0
#ifStl=0,jump to exit1
#a0=num
#Sal=i
#call compare
#i×4
#Stl=array[i]
#store result to array[i]
#i=i+1
#restore $ra
#restore $fp
#restore $sl
#free stack frame
#return to caller
(2)过程 compare。入口参数为a和b,有一个返回参数,没有局部变量,被调用过程为
sub。所以其栈帧中除了保留所用的保存寄存器外,还必须保留返回地址$ra和旧$fp的
值;因为compare过程的参数和 sub过程的入口参数一样,所以在调用sub前没有对$a0
和$al寄存器送参数。
compare:addi
sw
Sw
addj
jal
slt
beq
move
j
else:
ori
exit2:
1w
lw
addj
jr
Ssp,$sp,-8
Sra,4($sp)
Sfp,0(Ssp)
sfp,Ssp,4
sub
$t1, $v0,Szero
St1, Szero, else
$v0,$zero
exit2
5v0,$zero,1
$fp, 0($sp)
Sra,4(Ssp
Ssp,$sp,8
$ra
#save $fp on stack
#set Sfp
#if Sv0<0$tl=1 else$t1=0
#if St1=0,jump to else
#return 0
#return 1
(3)过程sub。入口参数为a和b,有一个返回参数,没有局部变量,是叶子过程,且过
程体中没有用到任何保存寄存器。所以栈帧中不需要保留任何信息。
sub:sub
jr
Sv0,Sa0, sal
$ra
需要说明的是,上例给出的程序是示意性的,实际上该程序没有任何意义。因为过程
set_array所做的工作就是把比较的结果写到数组 array中,没有任何返回值,而数组 array
是局部变量,当从过程 set_array返回后,该过程的栈帧全部被释放,array 中的值也全部无
效,所以程序没有做任何有结果的工作。此外,从上述例子可以看出,如果全部利用栈指针