# A.7 指令集编码
<!-- source: p0688 -->

前面各项选择显然会影响指令如何编码为处理器执行的二进制表示。表示方式不仅影响编译后的程序大小，也影响处理器实现：处理器必须对其译码，迅速找出操作及操作数。操作通常由一个称为**操作码**的字段指定。下面将看到，关键决定是如何将寻址方式与操作一起编码。

这一决定取决于寻址方式的种类，以及操作码与寻址方式之间的独立程度。某些较早的计算机有一至五个操作数，每个操作数又有十种寻址方式（见图 A.6）。面对如此多的组合，通常需要为每个操作数设置独立的地址说明符，说明如何访问它。另一个极端是加载—存储计算机，它们只有一个存储器操作数，而且只有一两种寻址方式；此时显然可将寻址方式直接编码在操作码中。

寄存器数量与寻址方式数量都会显著影响指令大小，因为寄存器字段和寻址方式字段可能在同一条指令中出现多次。实际上，对大多数指令而言，寻址方式与寄存器字段消耗的位数，远多于操作码。编码指令集时，设计者必须权衡几种互相竞争的要求：

1. 希望拥有尽可能多的寄存器和寻址方式。
2. 寄存器与寻址方式字段的大小，会影响平均指令大小，继而影响平均程序大小。
3. <!-- source: p0689 -->希望指令长度便于流水线实现处理。易于译码的指令有何价值，将在附录 C 与第 3 章讨论。至少，指令应为整数字节长度，而非任意位数。许多桌面与服务器设计者选择定长指令，以牺牲平均代码大小为代价，获得实现上的好处。

图 A.18 展示三种常见编码方案。第一种称为**可变编码**，因为几乎所有操作都可以使用全部寻址方式；当寻址方式和操作都很多时，它最合适。第二种称为**固定编码**，将操作与寻址方式合并到操作码中；它通常只有一种指令长度，适合操作和寻址方式都较少的情形。二者的权衡是程序大小与处理器译码难易程度。可变编码力求用尽可能少的位表示程序，<!-- source: p0690 -->但不同指令的长度与所完成工作量可能差异很大。

![可变长度、固定长度与混合指令编码格式](/figures/app-a/fig-a-18.png)
{#fig-A.18 .wide .expand num="A.18" caption="指令编码的三种基本形式：变长、定长和混合。变长格式可支持任意数量的操作数，由每个地址说明符确定相应操作数的寻址方式及说明符长度；通常代码最小，因为无需包含未使用的字段。定长格式始终具有相同操作数数量，寻址方式若有选择，则作为操作码的一部分；它通常产生最大的代码。字段位置往往固定，但不同指令可以将其用于不同目的。混合方法用操作码指定多种格式，增加一或两个字段指定寻址方式，再用一或两个字段指定操作数地址。"}

图内术语：Operation and no. of operands＝操作及操作数数量；Operation＝操作；Address specifier＝地址说明符；Address field＝地址字段。(A) 变长：Intel 80x86、VAX；(B) 定长：RISC-V、ARM、MIPS、PowerPC、SPARC；(C) 混合：RISC-V Compressed（RV32IC）、IBM 360/370、microMIPS、Arm Thumb2。

看一条 80x86 指令，就能说明可变编码：

~~~text
add EAX,1000(EBX)
~~~

`add` 表示两个操作数的 32 位整数加法，操作码占 1 字节。80x86 的地址说明符占 1 或 2 字节，指定源／目的寄存器 EAX，以及第二个操作数的寻址方式（此处为位移）和基址寄存器 EBX；这一组合用 1 字节即可指定。处于 32 位模式时（见附录 K），地址字段为 1 或 4 字节。由于 1000 大于 \(2^8\)，该指令总长为：

$$
1+1+4=6\ \text{字节}
$$

80x86 指令长度在 1–17 字节之间。80x86 程序通常比采用固定格式的 RISC 程序更小（见附录 K）。

在可变与固定这两个极端之间，很自然会想到第三种方案：减小可变体系结构中指令大小与工作量的差异，同时提供多种指令长度以减少代码大小。这就是混合方法，下面将给出实例。

## 减小 RISC 的代码大小

RISC 开始用于嵌入式应用后，32 位固定格式成为负担，因为这类应用重视成本，也就重视更小的代码。为此，几家厂商提供了新混合版本的 RISC 指令集，同时包含 16 位和 32 位指令。较窄指令支持更少的操作、较小的地址和立即数字段、较少的寄存器，并采用两地址格式，而非经典 RISC 的三地址格式。RISC-V 提供名为 RV32IC 的扩展，C 表示 compressed（压缩）。常见情形，例如较小的立即数，以及源、目的寄存器相同的常用 ALU 操作，都可以编码为 16 位格式。[^intermediates] 附录 K 还给出 ARM Thumb 和 microMIPS 两个实例，它们均声称代码大小最多可缩减 40%。

与这些指令集扩展不同，IBM 直接压缩标准指令集，并加入硬件，在指令缓存未命中、从存储器取指时解压。因此，指令缓存中仍保存完整的 32 位指令，主存、ROM 和磁盘上则保存压缩代码。RV32IC、microMIPS、Thumb2 等压缩格式的优点，是让指令缓存的有效容量仿佛增加约 25%；IBM CodePack 的优点则是编译器不必为不同指令集作修改，<!-- source: p0691 -->而且指令译码仍可保持简单。

CodePack 首先对任意 PowerPC 程序进行行程长度编码压缩，然后将得到的压缩表装入片上一个 2 KB 表中，因此每个程序都有自己的独特编码。压缩后，分支目标不再位于对齐的字边界；为处理这种情况，PowerPC 在存储器中建立哈希表，将压缩地址与未压缩地址相互映射。类似 TLB（见第 2 章），它缓存最近使用的映射，以减少访存次数。IBM 声称，整体性能代价为 10%，换来 35%–40% 的代码缩减。

## 小结：指令集编码

前几节关于指令集各组成部分的选择，决定设计者是否有条件在可变编码与固定编码之间作选择。若能够选择，重视代码大小甚于性能的设计者会选可变编码，重视性能甚于代码大小的设计者会选固定编码。RISC-V、MIPS 和 ARM 都有同时使用 16 位、32 位指令的扩展；代码大小受严格限制的应用，可以选择 16 位变体。附录 E 给出 13 个体现设计者选择结果的实例。[^appendix-reference] 附录 C 与第 3 章还将进一步讨论，指令的可变性如何影响处理器性能。

为 A.9 节介绍 RISC-V 指令集体系结构所需的铺垫已经接近完成。不过在此之前，简要考察编译技术及其对程序性质的影响，会很有帮助。

[^intermediates]: 译注：原文此处写作“intermediates with small values”；结合上下文的压缩指令字段，这里按小立即数理解。
[^appendix-reference]: 译注：原文此处指向附录 E；本文件中附录 E 为嵌入式系统，指令集体系结构调查位于附录 K。
