RV64-64位地址指令分析

发布时间 2023-11-17 08:23:45作者: 吴建明wujianming

RV64-64位地址指令分析

9.1 导言

图 9.1 至 9.4 是 RV32G 指令集的 64 位版本 RV64G 指令集的图示。由图可见,要切换 到 64 位 ISA,ISA 只添加了少数指令。指令集只添加了 32 位指令对应的字(word),双字 (doubleword)和长整数(long)版本的指令,并将所有寄存器(包括 PC)扩展为 64 位。因此, RV64I 中的 sub 操作的是两个 64 位数字而不是 RV32I 中的 32 位数字。RV64 很接近 RV32 但实际上又有所不同;它添加了少量指令同时基础指令做的事情与 RV32 中稍有不同。

例如,图 9.8 中 RV64I 版本的插入排序与图 2.8 中 RV32I 版本的插入 排序非常相似。它们指令数量和大小都相同。唯一的变化是加载和存储字指令变为加载并存 储双字,地址增量从对应字的 4(4 字节)变为对应双字的 8(8 字节)。

图 9.5 列出了图 9.1 到 9.4 中的 RV64GC 指令的操作码。 尽管 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 条指令都添加 了双字版本。

 

图 9.1:RV64I 指令图示。带下划线的字母从左到右连起来构成 RV64I 指令。灰色部分是扩展到 64 位寄 存器的旧 RV64I 指令,而暗(红色)部分是 RV64I 的新指令。

 

图 9.2:RV64M 和 RV64A 指令图示

 

图 9.3:RV64F 和 RV64D 指令图

 

图 9.4L:RV64C 指令图

 

RV64M Satndard Extension (in addition to RV32M)

 

RV64A Satndard Extension (in addition to RV32A)

 

RV64F Satndard Extension (in addition to RV32F)

 

RV64D Satndard Extension (in addition to RV32D)

 

图 9.5:RV64 基本指令和可选扩展指令的的操作码表。这张图包含了指令布局,操作码,格式类型和名称。

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 ABI 包括 lp64,lp64f 和 lp64d lp64 表示 C 语言中的长整型以及指针类型为 64 位; 整型仍然是 32 位。与 RV32(见第 3 章) 相同,后缀 f 和 d 表示如何传递浮点参数。

补充说明:RV64V 没有指令图示 是因为有动态寄存器类型的存在,它与 RV32V 的完全一致。唯一的变化是第 75 页的图 8.2 中的 X64 和 X64U 动态寄存器类型仅在 RV64V 中出现,RV32V 并不支持。

9.2 使用插入排序来比较 RV64 与其他 64 位 ISA 正如 Gordon Bell 在本章开头所说,一个架构致命的缺陷是用光了地址位。随着程序使 用的内存大小逐渐逼近 32 位地址空间的极限,不同指令集的架构师开始了设计他们指令集 的 64 位地址版本[Mashey 2009]。 最早的是 MIPS,在 1991 年,它将所有寄存器以及程序计数器从 32 扩展至 64 位并添 加了新的 64 位版本的 MIPS-32 指令。MIPS-64 汇编语言指令都以字母“d”开头,例如 daddu 或 dsll(参见图 9.10)。程序员可以在同一个程序中混合使用 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 版本(第 2 章第 30 页上图 2.11 中)和 x86-64 版本(图 9.11 中)的指令,可以发现 x86-64 指令集的优势。新的 64 位 ISA 将所有变量分配在寄存器 中,而不是像 x86-32 一样,要将多个变量保存到内存中,这将指令的数量从 20 条减少到了 15 条。尽管 64 位代码指令数量比 32 位少,但是代码大小实际上要大一个字节,从 45 变成了 46 字节。原因是为了挤进新的操作码以便操作更多的寄存器,x86-64 添加了一个前缀字。

 

图 9.6:四个 ISA 的插入排序的指令数和代码大小。 ARM Thumb-2 和 microMIPS 是 32 位地址 ISA,因 此不适用于 ARM-64 和 MIPS-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 2015]。 而且,它的指令数仍然在增长。自公布几年以来,ARM-64 已经经历了三次扩展。

图 9.9 中插入排序的 ARM-64 代码看起来更接近 RV64I 代码或 x86-64 代码,而不太像 ARM-32 代码。例如,因为有 31 个寄存器可用,就没有必要从堆栈中保存和恢复寄存器。 而且由于 PC 不再存放于通用寄存器中,ARM-64 单独增加了一条返回指令。 图 9.6 总结了插入排序在不同 ISA 下的指令数和字节数。图 9.8 到 9.11 显示了 RV64I, ARM-64,MIPS-64 和 x86-64 的代码。这四段代码注释中括号内的短语阐明了RV32I 版本与这些 RV64I 版本之间的差异。

MIPS-64 用到了最多的指令,主要是因为它需要使用 nop 指令来填充无法有效利用起 来的分支延迟槽。由于比较和分支用一条指令完成,而且分支指令没有延迟槽,RV64I 需要 的指令更少。虽然相较于 RV64I,对于每个分支,ARM-64 和 x86-64 需要多使用两条指令, 但它们的缩放寻址模式避免了 RV64I 中所需的地址算术指令,可以让它们少使用一些指令。 但是总的而言,RV64I + RV64C 代码大小要小得多,具体原因会在下一节阐述。

补充说明:ARM-64,MIPS-64 和 x86-64 不是官方名称 他们的官方名称是:所说的 ARM-64 实则是 ARMv8,MIPS-64 是 MIPS-IV 和 x86-64 是 AMD64。

 

图 9.7:RV64G,ARM-64 和 x86-64 与 RV64GC 的相对程序大小比较。使用了比图 9.6 中更大的程 序来进行对比。该图第 2 章中第 9 页的图 1.5 中的 32 位 ISA 比较的对应的 64 位 ISA 比较 .RV32C 代码 大小与 RV64C 几乎一致;仅比 RV64C 小 1%。 ARM-64 没有 Thumb-2 选项,因此其他 64 位 ISA 的代码 大小明显大于 RV64GC 代码。测量的程序是 SPEC CPU2006 基准测试,使用的 GCC 编译器[Waterman 2016]。 9.3 程序大小 图 9.7 比较了 RV64,ARM-64 和 x86-64 的平均相对代码大小。

将这个图见图 1.5 比较。首先,RV32GC 代码的大小与 RV64GC 几乎相同;它只比 RV64GC 小 1%。 RV32I 和 RV64I 的代码大小也很接近。而 ARM-64 代码比 ARM-32 代码小 8%,由于没有 64 位地址版本的 Thumb-2,所以所有指令都保持 32 位长。因此,ARM-64 代码比 ARMThumb2 代码大 25%。由于添加了前缀操作码以装下新的指令以及扩展的寄存器,x86-64 的代码比 x86-32 代码大 7%。

因此,就程序大小而言,RV64GC 更优秀,因为 ARM-64 代码比 RV64GC 大 23%,x86-64 代码比 RV64GC 大 34%。程序大小的差异如此得大,以至于 RV64 可以较 低的指令高速缓存缺失率来提供更高的性能,或者可以使用更小的指令缓存来降低成本,但 依然能提供令人满意的缺失率。

耗尽地址位是计算机体系结构的致命弱点,许多架构因为这个缺点而消亡。ARM-32 和 94 Thumb-2 仍然是 32 位架构,所以他们对大型程序没有帮助。像 MIPS-64 和 x86-64 这样的一 些 ISA 在转型中幸存下来,但 x86-64 并不是 ISA 设计的典范,而写这篇文章的时候,MIPS64 的前路依然迷茫。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 年,这样可以可以学习先行者们的好的设计并从他们的错误中吸取教训。