2.6分支指令
自动化计算机的实用性取决于循环使用一个给定指令序列的可能度,其循环次数依赖于计算结果。
--冯诺依曼
计算机和简单计算器不同的地方在于它能进行决策。根据不同的输入数据和中间计算结果,执行不同的指令。程序语言中,一般使用if语言表示分支,有时if语句和goto语句,标号(label)配合使用。MIPS汇编语言包括两条分支指令,它们类似带goto的if语句。第一条指令是
beq register1, register2, L1
这条指令的意思是:当Register1的值和Register2的值相等是,程序将分支执行标号为L1的语句。助记符beq表示如果相等则分支。第二条指令是:
bne register1, register2, L1
这条指令的意思是:当register1和register2的值不相等时,程序将分支执行标号位L1的语句。助记符bne表示如果不相等则分支。
通常,通过判断相反的条件来跳过if语句后面的then部分,代码的效率更高。
无条件分支指令:当遇到这条指令时,程序必须分支。
为了区分条件分支和无条件分支,MIPS将无条件分支指令命名为跳转(jump)指令简写为j
例如:j Exit #go to Exit
2.6.1循环
分支在if语句和循环程序中都起着重要作用:if语句中,用于二者选一;循环程序中用于重复计算。
Loop标签:我们需要添加Loop标签以便可以在循环的末端跳到开始的那条指令。
Loop: sll $t1, $s3, 2.
一个基本块是指这样一个指令序列:除了可能在序列结尾处,序列中没有分支指令;除可能在序列开始处,序列中没有分支目标和分支标号。编译的最初阶段就是将程序分成基本块。
slt $t0, $s3, $s4
表示当寄存器$s3的值小于寄存器$s4的值时,寄存器$t0被设置为1;否则设置为0.
也有立即数版的:slti
2.6.2 Case/Switch语句
实现switch语句的一种方法是,借助一个条件判断序列,将switch语句转化为一系列if - then-else语句。
有时,另一种更有效的方法是通过编码形成一个分支指令序列地址表,即转移地址表;程序通过查找转移地址表以获取目标地址,并跳转到相应的分支指令序列。转移地址表是一个字数组,数组中的各个元素对应于代码中各个标号的地址。
为了支持上面这种情况,MIPS这类计算机包含一条寄存器跳转指令jr,实现无条件地转移到某个寄存器指定的地址。
2.7计算机硬件的过程支持
过程或函数是Chuo或java程序员进行结构化编程的一种工具,两者均有助于提高程序的可读性和代码的可重用性.过程允许程序员使用参数将过程与其余的程序和数据分离开,只允许传值和返回结果,从而每次将精力只集中在任务的一部分.在这节的结束部分我们描述了java的等价表示,但java对计算机的要求与C语言相同.
可以将过程想象成一个侦探,他离开时带着秘密计划,获得资源,执行任务,掩盖足迹,带着要求的结果返回初始点.一旦任务完成则应该不产生任何干扰.更重要的是,侦探是在"需要确切知道"的基础上操作,所以侦探不能对雇主有任何臆断.
同样,在过程运行期间,程序必须遵循以下六个步骤:
(1) 将参数存放在过程可以获取到的位置
(2) 向过程传递控制。
(3) 获得过程所需的存储资源
(4) 执行需要的任务
(5) 将结果的值放在调用程序可以获取到的地方
(6) 将控制返回到初始点,因为一个过程可能由一个程序中的几个点调用。
寄存器是计算机中保存数据最快的存储位置,所以我们希望尽量多地使用寄存器。MIPS软件在为过程调用分配32位寄存器时遵循以下规定:
--$a0 ~ $a3: 四个参数寄存器,用于传递参数。
--$v0 ~ $v1: 两个储值寄存器,用于返回值。
--$ra: 一个返回地址寄存器,用于返回起始点。
除了分配寄存器,MIPS汇编语言包括一个过程指令:跳转到某个地址的同时将下一条指令的地址保存在寄存器$ra。这条跳转-链接指令简写为:
jal ProcedureAddress
指令名中的链接(link)部分表示指向调用位置的地址或链接,使过程可以返回到合适的地址。
链接存储在$ra寄存器中,称为返回地址。返回地址是必要的因为相同的过程可能在程序的不同部分调用。
用一个寄存器保存当前运行的指令地址,这在存储程序概念中是绝对必要的。初遇历史原因,这个寄存器通常称为程序计数器(其更合理的名字是指令地址寄存器),在MIPS体系结构中简写为PC。jal指令将PC+4保存在$ra寄存器中,这样链接到下一条指令从而令过程可以返回。
寄存器跳转指令--jr, 用于非条件跳转到寄存器中指定的地址:jr $ra
调用程序或称为调用者将参数值放在$a0~$a3中,使用jal X跳转到过程X(有时称为被调用者)。被调用者执行运算,将结果放在$v0~$v1,然后使用jr $ra 将控制返回到调用者。
2.7.1 使用更多寄存器
MIPS软件位栈分配了另一个寄存器:栈指针($sp),用于保存被调用者所需的寄存器。因为历史先例,栈“增长”是按照从高到低的地址顺序。这条规定意味着将值压栈时,栈指针减小;而值出栈时,栈长度缩短,栈指针增大。
MIPS将18个寄存器分为两组:
--$t0 ~ $t9: 10个临时寄存器,在过程调用中被调用者(被调用的过程)不必保存。
--$s0 ~ $s7: 8个保留寄存器,在过程调用中必须被保存
2.7.2嵌套过程
没有调用其他过程的过程称为叶过程。
假如,假设主程序调用过程A,参数为3时,将值3存入存储器$a0然后使用jal A。再假设过程A通过jal B调用过程B,参数为7,同样存入$a0。因为A尚未结束任务,所以存在寄存器$a0使用上的冲突。同样在寄存器$ra存在返回地址的冲突,因为它现在保存着B的返回地址。
一个解决办法是将其他所有必须保存的寄存器压栈。$sp(栈指针)本身的保存是通过被调用者将其被减去的值重新加上,其他寄存器则通过将它们保存到栈(如果被使用的话)再从栈中恢复它们来进行保存。这些动作也确保了调用者在退栈是能取到与压栈时存入的相同的值,因为被调用者在退栈时能取到与压栈时存入的相同的值,因为被调用者能够保证保护了$sp并且因为被调用者还保证不修改退栈中的调用者的入口地址,在这些过程调用时均保存在$sp以上的区域。
2.7.3 在栈中为新数据分配空间
栈中包含过程保存的寄存器和局部变量的段称为过程帧或活动记录。一些MIPS软件使用帧指针($fp)指向过程帧的第一个字。我们可以避免在过程中修改$sp来保护$fp。
2.7.4在堆中为新数据分配空间
堆位于静态数据段中之后的区域。注意这种分配允许堆和栈的相互增长,因此在两个段此消彼长的过程中达到内存的高效使用。C语言通过显式的函数调用在堆上分配和释放空间。malloc()在堆上分配空间并返回指向它的指针,free()释放指针指向的栈空间。
2.8人机交互
ASCII码:
可以使用一系列指令从一个字中析取出另一个字,所以字的读取和存储足以传送字节和字.然而,由于一些程序中文本的流行,所以MIPS仍然提供号字节转移指令.字节读取从内存中读出一个字节,放在寄存器的最右边8位.字节存储把寄存器最右边的8位取出来然后写到内存中.所以我们复制一个字节的顺序如下:
lb $t0, 0($sp) #read byte from source
sb $t0, 0($gp) #write byte to distination
字符通常连接为字符数目可变的字符串。有三种表示一个字符串的选择:
①字符串的第一个位置保留以给出字符串的长度
②附加带有字符串长度(如在结构体中)的变量
③字符串最后一个位置用一个字符来标识其结尾。
C使用第三种方法,用一个值为0的字节来结束字符串。所以字符串"cal"在C中用4字节表示。
Java中的字符和字符串
Unicode是大多数人类语言中字母的通用编码。Unicode中字母数和ASCII编码中有用的字符数一样多。为了更有包容性,Java为字符使用Unicode。它默认使用16位来表示一个字符。MIPS指令集具有外在的指令来读取和存储这样的16位半字。lh(load half)从存储器中读出一个半字,然后放在寄存器的最右边的16位。sh(store half)读出寄存器最右边的16位然后写进存储器。
字符串是带有专门的内置设置支持和用于连接、比较、转换的预定义方法的标准的Java类与C语言不同的是Java包括一个字来给出字符串长度,和Java数组相似。
2.9 对32位立即数的MIPS编址和寻址
虽然保证所有的MIPS指令为32位长简化了硬件,但有时使用32位常量会32位地址会更方便。本节先介绍较大常量的一般解决方法,然后描述了在分支和跳转语句中指令寻址采用的优化。
2.9.1 32 位立即数
虽然常量往往比较短而且适合16位字段,有时也比较打,MIPS指令集包括指令load upper immediat(取立即数高位)(lui)专门用于给寄存器中的常量设置高16位,允许后续指令设定常量的低16位。
lui指令将16位立即数字段的值存储到寄存器的高16位,用0填充低16位。
编译器或汇编程序必须把大的常数分开然后重组在一个寄存器中。像你想的那样,对内存地址饿得存取和立即数指令中的常数而言,立即数字段的大小限制确实存在问题。如果这个工作由汇编程序做,像MIPS软件一样,那么汇编程序必须有一个临时寄存器用于产生长整数值。这是给汇编程序保留$at寄存器的原因。
所以,MIPS机器语言的符号表示不再受硬件限制,而受汇编程序的构造者选择包括的内容所限。我们以靠近硬件层的方式来解释计算机的体系结构,注意当我们使用汇编程序的扩展语言时,那是在实际处理器中找不到的。
构造32位常数时要小心。addi指令将指令最左边的16位立即数字段复制到一个字的高16位中。2.5节的立即数逻辑或操作把0读到高16位中,所以被汇编程序用于和lui一起构造32位常数。
2.9.2分支和跳转中的寻址
MIPS跳转指令寻址最为简单。它们使用最后一种MIPS指令格式,叫做J型,包括6位操作码,其余的位为地址字段。如:j 10000。
和跳转指令不同,条件分支指令必须指定两个操作数和分支地址,如bne,beq等。如果程序地址必须适应16位域,那意味着没有大于2的16次方的程序,这在今天是一个很不现实的选择.一个可选的办法是指定一个总是加上分支地址的寄存器,所以一个分支指令可能如下计算:
程序计数器 = 寄存器 + 分支地址.
这个和允许程序的大小达到2的32次方,仍能使用条件分支解决分支大小问题.然后问题是指定哪个寄存器呢?
答案取决于分支条件是怎样采用的.条件分支在循环和if语句中使用,它们倾向与转到一个附近的指令.
PC相对寻址:因为程序计数器(PC)包括了当前指令地址,我们可以转移到离当前指令距离位±2的十五次方个字的地方,如果我们使用PC来作为增加地址的寄存器。几乎所有循环和if语句都比2的十六次方小的多,所以PC是一个理想的选择。对硬件来说递增PC来指向下一条指令是很方便的。所以,MIPS寻址事实上是和下一条指令地址相关(PC+4),而不是当前指令PC。
MIPS体系结构通过对跳转和跳转-链接指令使用J-类型格式来为过程调用提供长地址。
因为MIPS指令都是4字节长,MIPS通过使用下一条指令地址为字数而不是字节数的PC相关寻址来延伸分支距离。所以,16位的字地址,相比16位的字节地址,跳转范围扩大了4倍。
2个字 = 8个字节
2.9.3MIPS寻址模式总结
多种不同寻址形式一般统称为寻址模式。MIPS寻址模式如下所示:
(1) 寄存器寻址,操作数是寄存器。
(2) 基址或偏移寻址,操作数在内存中,其地址是指令中基址寄存器和常数的和。
(3)立即数寻址,操作数是指令中的常数
(4)PC相对寻址,地址是PC计数器和指令中常数的和。
(5)伪直接寻址,跳转地址是指令中26位和PC计数器的高位相连而成。
2.9.4机器语言解码
根据逆向工程将机器语言恢复到源汇编语言。
2.10程序的翻译和启动运行
2.10.1编译器
编译器将C程序转换成一种机器能理解的符号形式的汇编语言程序
2.10.2汇编器
汇编器也能处理一些机器语言指令的常见变种,就像这些变种是它自己的指令一样。硬件不需要实现这些指令,;然而,它们在汇编语言中的存在简化了程序变换和编程、这类指令称为伪指令。总的来说,伪指令使MIPS拥有一个比已由硬件实现的更为丰富的汇编语言指令集。唯一的代价是保留了一个由汇编器使用的寄存器$at
2.10.3链接器
把所有独立汇编的机器语言程序拼接在一起