跳转到主要内容

A.2 指令集体系结构的分类

处理器内部存储的类型是最基本的区别,因此本节集中讨论体系结构这一部分的不同选择。主要方案是栈、累加器或一组寄存器。操作数可以显式指定,也可以隐含指定:栈体系结构的操作数隐含在栈顶;累加器体系结构则隐含地以累加器作为一个操作数。通用寄存器体系结构只使用显式操作数,即寄存器或存储单元。图 A.1 给出这些体系结构的框图,图 A.2 展示代码序列 C=A+BC=A+B 在这三类指令集中通常如何表示。根据体系结构类别和具体指令的选择,显式操作数可以直接从存储器访问,也可能需要先装入临时存储位置。

这些图说明,寄存器计算机实际上分为两类。一类允许任意指令在执行时访问存储器,称为寄存器—存储器体系结构;另一类只允许加载和存储指令访问存储器,称为加载—存储体系结构。第三类把所有操作数放在存储器中,称为存储器—存储器体系结构,但如今销售的计算机中已经没有这一类。有些指令集体系结构的寄存器多于单一累加器,但限制了这些专用寄存器的用途;这种体系结构有时称为扩展累加器计算机或专用寄存器计算机。

虽然早期计算机大多采用栈或累加器体系结构,但 1980 年之后设计的几乎所有新体系结构,都采用加载—存储寄存器体系结构。通用寄存器(general-purpose register,GPR)计算机兴起,主要有两个原因。第一,寄存器与处理器内其他存储形式一样,速度比存储器快。第二,对于编译器而言,寄存器比其他内部存储形式更容易高效利用。例如,在寄存器计算机上,计算表达式 (A×B)+(B×C)−(A×D)(A\times B)+(B\times C)-(A\times D) 时,可以按任意次序进行乘法;考虑到操作数的位置或流水线因素,这种自由可能提高效率(见第 3 章)。但在栈计算机中,由于操作数隐藏在栈内,硬件只能按一种次序计算表达式,而且可能需要多次装入同一个操作数。

四类指令集体系结构中的操作数位置与 ALU 数据流
图 A.1 四类指令集体系结构中的操作数位置。箭头表示操作数是算术逻辑单元(ALU)运算的输入、结果,还是同时兼作输入和结果。浅色表示输入,深色表示结果。在 (A) 中,栈顶(TOS)寄存器指向最上方的输入操作数,它与下方操作数一起运算;第一个操作数从栈中移除,结果取代第二个操作数,TOS 更新为指向结果,全部操作数均隐含指定。在 (B) 中,累加器既是隐含的输入操作数,也是结果位置。在 (C) 中,一个输入在寄存器中,另一个在存储器中,结果写入寄存器。(D) 的全部操作数都是寄存器;与栈体系结构一样,只有单独的指令才能在这些位置与存储器之间传送数据:(A) 使用 push/pop,(D) 使用 load/store。

图内术语:Processor=处理器;Memory=存储器;Stack=栈;Accumulator=累加器;Register-memory=寄存器—存储器;Register-register/load-store=寄存器—寄存器/加载—存储。

栈累加器寄存器(寄存器—存储器)寄存器(加载—存储)
Push ALoad ALoad R1,ALoad R1,A
Push BAdd BAdd R3,R1,BLoad R2,B
AddStore CStore R3,CAdd R3,R1,R2
Pop CStore R3,C
图 A.2 四类指令集实现 C=A+B 的代码序列。栈和累加器体系结构中的 Add 使用隐含操作数,寄存器体系结构使用显式操作数。假定 A、B、C 均位于存储器中,且 A、B 的原值不能被破坏。图 A.1 展示各类体系结构的 Add 操作。

更重要的是,寄存器可以保存变量。将变量分配到寄存器之后,存储器流量减少,程序执行加快(因为寄存器比存储器快),代码密度也提高(因为指定一个寄存器所需的位数比指定一个存储单元少)。

A.8 节将说明,编译器作者希望所有寄存器地位相同,且不保留作特定用途。较早的计算机把一些寄存器分配给专门用途,违背了这一期望,实质上减少了通用寄存器的数量。如果真正通用的寄存器太少,尝试将变量分配到寄存器就得不偿失;编译器反而会把所有尚未指定用途的寄存器都留给表达式求值。

多少寄存器才足够?答案当然取决于编译器的有效性。多数编译器会留出一些寄存器计算表达式,用一些寄存器传递参数,再将其余寄存器分配给变量。现代编译技术能够有效使用更多寄存器,促使较新的体系结构增加了寄存器数量。

GPR 体系结构可按两项主要指令集特征分类。这两项都与典型算术或逻辑指令(ALU 指令)的操作数有关。第一项是 ALU 指令采用两个还是三个操作数。三操作数格式包含一个结果操作数和两个源操作数;两操作数格式则让其中一个操作数同时充当源和结果。第二项是 ALU 指令中有多少个操作数可以是存储器地址。典型 ALU 指令支持的存储器操作数可以从零个到三个。图 A.3 列出这两个属性的组合及相应计算机实例。虽然共有七种可能组合,但其中三种足以对几乎所有现有计算机分类。如前所述,它们是加载—存储(也称寄存器—寄存器)、寄存器—存储器,以及存储器—存储器。

存储器地址数允许的最大操作数总数体系结构类型实例
03加载—存储ARM、MIPS、PowerPC、SPARC、RISC-V
12寄存器—存储器IBM 360/370、Intel 80x86、Motorola 68000、TI TMS320C54x
22存储器—存储器VAX(也有三操作数格式)
33存储器—存储器VAX(也有两操作数格式)
图 A.3 典型 ALU 指令的存储器操作数与操作数总数的常见组合,以及计算机实例。每条 ALU 指令均不访问存储器的计算机,称为加载—存储或寄存器—寄存器计算机。按一条典型 ALU 指令包含一个还是多个存储器操作数,分别称为寄存器—存储器或存储器—存储器。

图 A.4 给出各方案的优缺点。它们当然不是绝对结论,而是定性描述;实际影响取决于编译器与实现策略。即使 GPR 计算机提供存储器—存储器操作,编译器也完全可以忽略这些操作,将它当作加载—存储计算机使用。体系结构最普遍的影响之一,是改变指令编码以及完成一个任务所需的指令数。附录 C 和第 3 章将讨论这些体系结构方案如何影响具体实现方法。

类型优点缺点
寄存器—寄存器 (0, 3)指令编码简单、定长;代码生成模型简单;各指令执行所需时钟周期数相近(见附录 C)。相比在指令中访问存储器的体系结构,指令数更多。指令数较多、指令密度较低,使程序更大,可能影响指令缓存。
寄存器—存储器 (1, 2)可以直接访问数据,无需先执行单独的加载指令;指令格式通常易于编码,代码密度较高。操作数地位不对等,因为二元运算会破坏一个源操作数。每条指令都要编码寄存器号和存储器地址,可能限制寄存器数。每条指令的周期数随操作数位置而异。
存储器—存储器 (2, 2) 或 (3, 3)最紧凑;无需占用寄存器保存临时值。指令大小差异很大,尤其是三操作数指令;每条指令完成的工作量也差异很大。存储器访问造成瓶颈。(如今已不使用。)
图 A.4 三种最常见 GPR 计算机的优缺点。(m, n) 表示 m 个存储器操作数、n 个操作数总数。一般而言,选择较少的计算机使编译器少做决策,从而简化编译器工作(见 A.8 节);具有大量灵活指令格式的计算机,则能减少程序编码所需位数。寄存器数也会影响指令大小,因为指令中每个寄存器标识需要 log₂(寄存器数)位。因此,对寄存器—寄存器体系结构,将寄存器数加倍需要额外 3 位,约占一条 32 位指令的 10%。

小结:指令集体系结构的分类

本节及 A.3–A.8 各节末尾,将总结一种新指令集体系结构应当具备的特征,为 A.9 节介绍的 RISC-V 体系结构奠定基础。从本节可以明确看出,新设计应采用通用寄存器。结合图 A.4 与附录 C 对流水线的讨论,可以进一步预期,它应当是一种采用加载—存储方式的通用寄存器体系结构。

讨论了体系结构类别,接下来考察操作数寻址。

表格阅读