A.2 指令集体系结构的分类
处理器内部存储的类型是最基本的区别,因此本节集中讨论体系结构这一部分的不同选择。主要方案是栈、累加器或一组寄存器。操作数可以显式指定,也可以隐含指定:栈体系结构的操作数隐含在栈顶;累加器体系结构则隐含地以累加器作为一个操作数。通用寄存器体系结构只使用显式操作数,即寄存器或存储单元。图 A.1 给出这些体系结构的框图,图 A.2 展示代码序列 在这三类指令集中通常如何表示。根据体系结构类别和具体指令的选择,显式操作数可以直接从存储器访问,也可能需要先装入临时存储位置。
这些图说明,寄存器计算机实际上分为两类。一类允许任意指令在执行时访问存储器,称为寄存器—存储器体系结构;另一类只允许加载和存储指令访问存储器,称为加载—存储体系结构。第三类把所有操作数放在存储器中,称为存储器—存储器体系结构,但如今销售的计算机中已经没有这一类。有些指令集体系结构的寄存器多于单一累加器,但限制了这些专用寄存器的用途;这种体系结构有时称为扩展累加器计算机或专用寄存器计算机。
虽然早期计算机大多采用栈或累加器体系结构,但 1980 年之后设计的几乎所有新体系结构,都采用加载—存储寄存器体系结构。通用寄存器(general-purpose register,GPR)计算机兴起,主要有两个原因。第一,寄存器与处理器内其他存储形式一样,速度比存储器快。第二,对于编译器而言,寄存器比其他内部存储形式更容易高效利用。例如,在寄存器计算机上,计算表达式 时,可以按任意次序进行乘法;考虑到操作数的位置或流水线因素,这种自由可能提高效率(见第 3 章)。但在栈计算机中,由于操作数隐藏在栈内,硬件只能按一种次序计算表达式,而且可能需要多次装入同一个操作数。

图内术语:Processor=处理器;Memory=存储器;Stack=栈;Accumulator=累加器;Register-memory=寄存器—存储器;Register-register/load-store=寄存器—寄存器/加载—存储。
| 栈 | 累加器 | 寄存器(寄存器—存储器) | 寄存器(加载—存储) |
|---|---|---|---|
Push A | Load A | Load R1,A | Load R1,A |
Push B | Add B | Add R3,R1,B | Load R2,B |
Add | Store C | Store R3,C | Add R3,R1,R2 |
Pop C | Store R3,C |
更重要的是,寄存器可以保存变量。将变量分配到寄存器之后,存储器流量减少,程序执行加快(因为寄存器比存储器快),代码密度也提高(因为指定一个寄存器所需的位数比指定一个存储单元少)。
A.8 节将说明,编译器作者希望所有寄存器地位相同,且不保留作特定用途。较早的计算机把一些寄存器分配给专门用途,违背了这一期望,实质上减少了通用寄存器的数量。如果真正通用的寄存器太少,尝试将变量分配到寄存器就得不偿失;编译器反而会把所有尚未指定用途的寄存器都留给表达式求值。
多少寄存器才足够?答案当然取决于编译器的有效性。多数编译器会留出一些寄存器计算表达式,用一些寄存器传递参数,再将其余寄存器分配给变量。现代编译技术能够有效使用更多寄存器,促使较新的体系结构增加了寄存器数量。
GPR 体系结构可按两项主要指令集特征分类。这两项都与典型算术或逻辑指令(ALU 指令)的操作数有关。第一项是 ALU 指令采用两个还是三个操作数。三操作数格式包含一个结果操作数和两个源操作数;两操作数格式则让其中一个操作数同时充当源和结果。第二项是 ALU 指令中有多少个操作数可以是存储器地址。典型 ALU 指令支持的存储器操作数可以从零个到三个。图 A.3 列出这两个属性的组合及相应计算机实例。虽然共有七种可能组合,但其中三种足以对几乎所有现有计算机分类。如前所述,它们是加载—存储(也称寄存器—寄存器)、寄存器—存储器,以及存储器—存储器。
| 存储器地址数 | 允许的最大操作数总数 | 体系结构类型 | 实例 |
|---|---|---|---|
| 0 | 3 | 加载—存储 | ARM、MIPS、PowerPC、SPARC、RISC-V |
| 1 | 2 | 寄存器—存储器 | IBM 360/370、Intel 80x86、Motorola 68000、TI TMS320C54x |
| 2 | 2 | 存储器—存储器 | VAX(也有三操作数格式) |
| 3 | 3 | 存储器—存储器 | VAX(也有两操作数格式) |
图 A.4 给出各方案的优缺点。它们当然不是绝对结论,而是定性描述;实际影响取决于编译器与实现策略。即使 GPR 计算机提供存储器—存储器操作,编译器也完全可以忽略这些操作,将它当作加载—存储计算机使用。体系结构最普遍的影响之一,是改变指令编码以及完成一个任务所需的指令数。附录 C 和第 3 章将讨论这些体系结构方案如何影响具体实现方法。
| 类型 | 优点 | 缺点 |
|---|---|---|
| 寄存器—寄存器 (0, 3) | 指令编码简单、定长;代码生成模型简单;各指令执行所需时钟周期数相近(见附录 C)。 | 相比在指令中访问存储器的体系结构,指令数更多。指令数较多、指令密度较低,使程序更大,可能影响指令缓存。 |
| 寄存器—存储器 (1, 2) | 可以直接访问数据,无需先执行单独的加载指令;指令格式通常易于编码,代码密度较高。 | 操作数地位不对等,因为二元运算会破坏一个源操作数。每条指令都要编码寄存器号和存储器地址,可能限制寄存器数。每条指令的周期数随操作数位置而异。 |
| 存储器—存储器 (2, 2) 或 (3, 3) | 最紧凑;无需占用寄存器保存临时值。 | 指令大小差异很大,尤其是三操作数指令;每条指令完成的工作量也差异很大。存储器访问造成瓶颈。(如今已不使用。) |
小结:指令集体系结构的分类
本节及 A.3–A.8 各节末尾,将总结一种新指令集体系结构应当具备的特征,为 A.9 节介绍的 RISC-V 体系结构奠定基础。从本节可以明确看出,新设计应采用通用寄存器。结合图 A.4 与附录 C 对流水线的讨论,可以进一步预期,它应当是一种采用加载—存储方式的通用寄存器体系结构。
讨论了体系结构类别,接下来考察操作数寻址。