一、导言
图 1 至 4 是 RV32G 指令集的 64 位版本 RV64G 指令集的图示。由图可见,要切换到 64 位 ISA,ISA 只添加了少数指令。指令集只添加了 32 位指令对应的字(word),双字(doubleword)和长整数(long)版本的指令,并将所有寄存器(包括 PC)扩展为 64 位。因此,RV64I 中的 sub 操作的是两个 64 位数字而不是 RV32I 中的 32 位数字。RV64 很接近 RV32但实际上又有所不同;它添加了少量指令同时基础指令做的事情与 RV32 中稍有不同。
尽管 RV64I 有 64 位地址且默认数据大小为 64 位,32 位字仍然是程序中的有效数据类型。因此,RV64I 需要支持字,就像 RV32I 需要支持字节和半字一样。更具体地说,由于寄存器现在是 64 位宽,RV64I 添加字版本的加法和减法指令:addw,addiw,subw。这些指令将计算结果截断为 32 位,结果符号扩展后再写入目标寄存器。 RV64I 也包括字版本的移位指令(sllw,slliw,srlw,srliw,sraw,sraiw),以获得 32 位移位结果而不是 64 位移位结果。要进行 64 位数据传输,RV64 提供了加载和存储双字指令:ld,sd。最后,就像 RV32I 中有无符号版本的加载单字节和加载半字的指令,RV64I 也有一个无符号版本的加载字:lwu。
出于类似的原因,RV64 需要添加字版本的乘法,除法和取余指令:mulw,divw,divuw,remw,remuw。为了支持对单字及双字的同步操作,RV64A为其所有的11条指令都添加了双字版本。
RV64F 和 RV64D 添加了整数双字转换指令,并称它们为长整数,以避免与双精度浮点数据混淆:fcvt.l.s,fcvt.l.d,fcvt.lu.s,fcvt.lu.d,fcvt.s.l,fcvt.s.lu,fcvt.d.l,fcvt.d.lu. 由于整数 x 寄存器现在是 64 位宽,它们现在可以保存双精度浮点数据,因此 RV64D 增加了两个浮点指令:fmv.x.w 和 fmv.w.x.
RV64 和 RV32 之间基本是超集关系,但是有一个例外是压缩指令。 RV64C 取代了一些RV32C 指令,因为其他一些指令对于 64 位地址可以取得更好的代码压缩效果。RV64C 放弃了压缩跳转并链接(c.jal)和整数和浮点加载和存储字指令(c.lw,c.sw,c.lwsp,c.swsp, c.flw,c.fsw,c.flwsp 和 c.fswsp)。在他们的位置,RV64C 添加了更受欢迎的字加减指令(c.addw,c.addiw,c.subw)以及加载和存储双字指令(c.ld,c.sd,c.ldsp,c.sdsp)。
二、使用插入排序来比较RV64与其他 64 位ISA
随着程序使用的内存大小逐渐逼近 32 位地址空间的极限,不同指令集的架构师开始了设计他们指令集的 64 位地址版本。最早的是 MIPS,在 1991 年,它将所有寄存器以及程序计数器从 32 扩展至 64 位并添加了新的 64 位版本的 MIPS-32 指令。MIPS-64 汇编语言指令都以字母“d”开头,例如 daddu 或 dsll。程序员可以在同一个程序中混合使用 MIPS-32 和 MIPS-64 指令。 MIPS-64 删除了 MIPS-32 中的加载延迟槽(流水线在侦测到写后读相关时会停止)。十年之后,是 x86-32 指令集也迎来了 64 位。架构师们在拓展地址空间的同时,也借机在 x86-64 中进行了一系列改进:
• 整数寄存器的数量从 8 增加到 16(r8-r15);
• 将 SIMD 寄存器的数量从 8 增加到 16(xmm8-xmm15)并且添加了 PC 相关数据寻址,以更好地支持与位置无关的代码。
• 添加了 PC 相关数据寻址,以更好地支持与位置无关的代码。这些改进部分缓和了 x86-32 指令集长久以来的一些弊端。
通过比较插入排序的 x86-32 版本,我们可以发现 x86-64 指令集的优势。新的 64 位 ISA 将所有变量分配在寄存器中,而不是像 x86-32 一样,要将多个变量保存到内存中,这将指令的数量从 20 条减少到了15 条。尽管 64 位代码指令数量比 32 位少,但是代码大小实际上要大一个字节,从 45 变成了 46 字节。原因是为了挤进新的操作码以便操作更多的寄存器,x86-64 添加了一个前缀字节来识别新指令。从 x86-32 到 x86-64 平均指令长度增长了。
又过了十年,ARM 也遇到了同样的地址问题。但是他们没有像 x86-64 那样,把旧的 ISA 扩展到支持 64 位地址。他们利用这个机会发明了一个全新的 ISA。从头设计一个新 ISA, 使得他们不必继承 ARM-32 的许多尴尬特性,他们重新设计了一个现代 ISA:
• 将整数寄存器的数量从 15 增加到 31;
• 从寄存器组中删除 PC;
• 为大多数指令提供硬连线到零的寄存器(r31);
• 与 ARM-32 不同,ARM-64 的所有数据寻址模式都适用于所有数据大小和类型;
• ARM-64 去除了 ARM-32 的加载存储多个数据的指令
• ARM-64 去除了 ARM-32 指令的条件执行选项。
ARM-32 的一些弱点依然存在于 ARM-64 指令集中:分支指令使用的条件码,指令中源和目标寄存器字段并不固定,条件移动指令,复杂寻址模式,不一致的性能计数器,以及只支持
32 位长度的指令。另外 ARM-64 无法切换到 Thumb-2 ISA,因为 Thumb-2 仅适用于 32 位地址。
与 RISC-V 不同,ARM 决定采用最大主义的方法来设计 ISA。虽然 ISA 比 ARM-32 更好,但它也更大。例如,它有超过 1000 条指令并且 ARM-64 手册有 3185 页。而且,它的指令数仍然在增长。自公布几年以来,ARM-64 已经经历了三次扩展。
插入排序的 ARM-64 代码看起来更接近 RV64I 代码或 x86-64 代码,而不太像ARM-32 代码。例如,因为有 31 个寄存器可用,就没有必要从堆栈中保存和恢复寄存器。而且由于 PC 不再存放于通用寄存器中,ARM-64 单独增加了一条返回指令。
图6 总结了插入排序在不同 ISA 下的指令数和字节数。MIPS-64 用到了最多的指令,主要是因为它需要使用 nop 指令来填充无法有效利用起来的分支延迟槽。由于比较和分支用一条指令完成,而且分支指令没有延迟槽,RV64I 需要的指令更少。虽然相较于 RV64I,对于每个分支,ARM-64 和 x86-64 需要多使用两条指令, 但它们的缩放寻址模式避免了 RV64I 中所需的地址算术指令,可以让它们少使用一些指令。但是总的而言,RV64I + RV64C 代码大小要小得多。
三、程序大小
图 7 比较了 RV64,ARM-64 和 x86-64 的平均相对代码大小。首先,RV32GC 代码的大小与 RV64GC 几乎相同;它只比 RV64GC 小 1%。RV32I 和 RV64I 的代码大小也很接近。而 ARM-64 代码比 ARM-32 代码小 8%,由于没有64 位地址版本的 Thumb-2,所以所有指令都保持 32 位长。因此,ARM-64 代码比 ARMThumb-2 代码大 25%。由于添加了前缀操作码以装下新的指令以及扩展的寄存器,x86-64 的代码比x86-32 代码大 7%。因此,就程序大小而言,RV64GC 更优秀,因为 ARM-64 代码比 RV64GC 大 23%,x86-64 代码比 RV64GC 大 34%。程序大小的差异如此得大,以至于 RV64 可以较低的指令高速缓存缺失率来提供更高的性能,或者可以使用更小的指令缓存来降低成本,但依然能提供令人满意的缺失率。
四、小结
耗尽地址位是计算机体系结构的致命弱点,许多架构因为这个缺点而消亡。ARM-32 和Thumb-2 仍然是 32 位架构,所以他们对大型程序没有帮助。像 MIPS-64 和 x86-64 这样的一些 ISA 在转型中幸存下来,但 x86-64 并不是 ISA 设计的典范,而写这篇文章的时候,MIPS- 64 的前路依然迷茫。ARM-64 是一个新的大型 ISA,时间会告诉我们它会有多成功。
RISC-V 受益于同时设计 32 位和 64 位架构,而较老的 ISA 必须依次设计它们。不出所料,对于 RISC-V 程序员和编译器编写者来说,32 位到 64 位之间的过渡是最简单的; RV64I ISA 几乎包含了所有 RV32I 指令。这也就是为什么我们只用两页参考卡片,就可以列出RV32GCV 和 RV64GCV 指令集。更重要的是,同步设计意味着 64 位架构指令集不必被狭窄的 32 位操作码空间限制。 RV64I 有足够的空间用于可选的指令扩展,特别是 RV64C,这使它成为代码大小比其他所有 64 位 ISA 都要小。我们认为 64 位架构更能体现 RISC-V 设计上的优越性,毕竟我们设计 64 位 ISA 比先行者们晚了 20 年,这样我们可以可以学习先行者们的好的设计并从他们的错误中吸取教训。
RV128 最初是作为 RISC-V 架构师内部的一个玩笑话,只是为了显示 128 位地址的 ISA 是可能的。但是,仓库规模的计算机可能很快就会拥有超过 264 字节存储容量的半导体存储器
(DRAM 和闪存),同时程序员可能会想要用访问内存的方式来访问这些存储。同时还有人提议使用 128 位地址来提高安全性。 RISC-V 手册确实指定了一个完整的 128 位 ISA 叫做 RV128G 。如图 1 至 4 所示,新增的指令基本上是与从 RV32 切换到 RV64 新增的指令类似。所有的寄存器也增长到 128 位,新的 RV128 指令要么指定了 128 位版本的指令(指令名称中使用 Q,意为四字(quadword))或其他指令的 64 位版本(指令名称中使用 D,意为双字(double word)的)。
|