本节书摘来自华章计算机《计算机系统:系统架构与操作系统的高度集成》一书中的第2章,第2.9节,作者:(美)拉姆阿堪德兰(Ramachandran, U.)(美)莱希(Leahy, W. D.)著, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
2.9 指令集体系结构选择
在本节中,我们总结关于指令集设计的体系结构的不同选择。这种选择存在于指令集中的算术/逻辑运算、寻址模式、体系结构类型,以及实际内存的布局(即指令格式)中。有时候,做出这些选择是由于当前的技术趋势和硬件的可行性考虑,有时候则是为了对高级语言结构提供精简而高效的支持。
2.9.1 额外的指令
有的体系结构提供了额外的指令,以提升编译出的代码的空间和时间的有效性。
例如,在MIPS体系结构中,加载和存储指令都是对于一整个32位的数进行操作的。然而,一旦指令被装到了寄存器中,特殊的指令就可以用来扩展其中某个特定的字节到另一个寄存器中;类似地,也可以在字中插入某个字节。
DEC的Alpha指令集包含了加载和存储不同粒度操作数的指令:字节、半字、字和四字。
一些体系结构包含预定义的立即数(如0、1及其他小整数),可以直接在指令中使用。
DEC VAX体系结构可以用单条指令来存储/加载寄存器文件中的所有寄存器到内存或者从内存中取出它们。也许有人会认为,这样的指令有利于在过程调用的时候进行寄存器的保存和恢复。读者应该根据本章前面的内容,考虑一下这种指令在过程调用中的实用性。
2.9.2 额外的寻址模式
在我们已经讨论过的寻址模式之外,有的体系机构还提供了更花哨的寻址模式。
许多体系机构提供了间接寻址模式:
ld @(ra)
在这条指令中,寄存器ra的内容是实际内存操作数的地址的地址。
伪直接寻址:
MIPS提供了这样一种类型的寻址模式,它使用PC的高6位以及它本身的低26位构成一个32位的有效地址:
早期的体系结构如IBM 360、PDP-11和VAX11支持的寻址模式远比我们在本章中讨论的多。大部分现代体系结构在访问的内存的寻址模式方面走的是最少化路线。这主要是因为,这么多年来,实际被编译器使用的复杂寻址模式非常少。甚至基址加偏移量在MIPS体系结构中也是没有的,尽管IBM PowerPC和Inter Pentium都支持这种模式。
2.9.3 体系结构类型
历史上曾经有过好几种不同的体系结构类型:
面向栈的体系结构 Burroughs Computers公司引入了面向栈的体系结构,在这种体系结构中,所有的操作数都是在栈上的。所有的指令都操作位于栈上的操作数。
面向内存的体系结构 IBM 360系列机着眼于面向内存的体系结构,大部分(如果不是全部的话)指令都是操作内存中的操作数。
面向寄存器的体系结构 正如在本章讨论的,这种体系结构中的大部分指令处理的是寄存器中的操作数。随着编译技术日趋成熟,以及处理器中寄存器的有效使用,这种风格的体系结构最终保留了下来。DEC Alpha和MIPS都是这种风格的体系结构。
混合类型 通常来说,针对特定的应用,可以选择其中某一种类型的体系结构。因此,很自然的,这些类型的混合体非常受欢迎。IBM PowerPC和Intel x86系列指令集都是面向内存和面向寄存器的混合类型。
2.9.4 指令格式
根据所有指令结构的不同,它们被分为以下几类:
- 零操作数指令
例子包括:
HALT(停止处理器)
NOP(什么都不做)
另外,如果体系结构是面向栈的,对于大部分指令(除了压栈和出栈的值是显式的),它们的操作数都是隐含的。在这种体系结构中指令看起来是这样的:
ADD(弹出栈顶的两个元素,将它们相加,并将结果压入栈中)
PUSH< operand>(将操作数压入栈中)
POP< operand>(将栈顶元素弹出作为操作数)
- 单操作数指令
这种类型的指令通常与高级语言中的一元运算符有关:
INC/DEC < operand>(将指定操作数增加或减少某个常数值)
NEG< operand>(取操作数的补码)
NOT< operand>(取操作数的反码)
另外,无条件转移指令通常也只有一个操作数:
J < target> (PC←target)
此外,一些老式机器(如DEC的PDP-8)使用一个隐含的操作数(称为累加器,ACC)和一个显式操作数。这种体系结构中的指令看起来是这样的:
ADD< operand> (ACC←ACC+operand)
STORE< operand> (operand←ACC)
LOAD< operand> (ACC←operand)
- 双操作数指令
这类指令同样映射为高级语言中的二元运算符。其基本思想是在二元运算中,一个操作数既是源操作数也是目标操作数。
ADD R1, R2 (R1←R1+R2)
移动数据的指令也属于这一类:
MOV R1, R2 (R1←R2)
- 三操作数指令
这是最常见的类型,在这一整章里我们都能看到它的例子,比如:
ADD Rdst,Rsrc1,Rsrc2(Rdst←Rsrc1 + Rsrc2)
LOAD R,Rb,offset(R←MEM[Rb+offset])
指令格式指的是指令如何在内存中布局。一个体系结构会包含上述各种风格的指令,由许多要求不同数量操作数的指令组成。
典型的指令具有下面的格式:
在设计指令格式的问题上,我们要考虑实际的实现。因为指令格式的选择对于设计的空间和时间有效性来说是个关键点。与之相关的选择是指令中各字段的实际编码,即在某个给定字段上用怎样的位串表达何种语义。例如,表示ADD的位串,等等。
广义上讲,所有指令格式分为两类:
所有指令等长 在这种格式中,所有的指令都有相同的长度(即一个内存字长度)。意思是指令的某一位根据指令的不同会有不同的含义:
优势:
指令长度固定简化了实现。
只要拿到了指令,马上就可以对它的各个字段进行解释,因为所有的指令都是定长的,且长度相同。
劣势:
因为各指令需要的空间其实是不一样的(例如单操作数指令和多操作数指令相比),这可能会造成空间的浪费。
我们需要一些连接逻辑(例如译码器和多路选择器)来将指令的各字段分配到数据通路的各元素中去。
指令集的设计者受限于所有指令长度固定(通常是一个字)的限制。这种限制体现在指令中立即值操作数的大小以及寻址时可指定的偏移量的范围。
MIPS就是使用定长指令的一个例子。
这是MIPS的一些指令的例子:
在上面的ADD指令中,跟在Rd后的5位字段是没有用的,但因为定长的需求只能保留下来。
指令长度可变 在这种格式中指令是变长的,即一条指令可能占多个字。
优势:
不会有空间浪费,因为每条指令都只占据了它所需的空间。
指令集设计者不再受限于有限的大小(例如,立即值的大小)。
有机会根据编译器对指令的使用情况,为操作码、寻址模式、操作数选择不同的大小和编码。
劣势:
这样的格式使实现更复杂了,因为指令的长度只有在解释了操作码后才能确定。这会导致指令的操作码和操作数需要顺序解释。
DEC VAX 11系列和Intel x86系列都是变长指令体系结构的例子。在VAX 11中,指令的长度在1~53字节变化。
需要强调的是,我们这里的目的并非暗示说本节介绍的所有体系结构类型和指令格式在今天都是可行的。我们的目的是让读者了解在指令集设计上有过许多曾经尝试过的选择。例如,过去曾经有过商业化的面向栈的体系结构(使用零操作数格式)和基于累加器的机器(使用单操作数指令格式)。然而,这些体系结构已经不再是通用处理器的主流。