这是 ABI 开始渗入 ISA 的情况之一。您会在 RISC-V 中找到其中的一些。由于我们在标准化 ISA 时移植了一个相当重要的软件堆栈,因此我们必须微调 ISA 以匹配真实代码。由于基本 RISC-V ISA 的一个明确目标是为未来的扩展保留大量可用的编码空间。
在这种情况下,ABI 设计决定是回答“是否存在类型的规范表示,当存储在寄存器中时,不需要这些寄存器提供的每个位模式来表示该类型可表示的每个值?” 在 RISC-V 的情况下,我们选择为所有类型强制规范表示。这里有一些 ISA 设计决策的反馈循环,我认为解决这个问题的最佳方法是通过一个示例来说明 ISA 与 ABI 共同发展的内容,而我们没有强制要求规范表示。
作为一个思考练习,让我们假设 RISC-V ABI 没有要求对int
存储在 RV64I 上的 X 寄存器中的高位进行规范表示。这里的结果是现有的 W 系列指令不会特别有用:您可以使用addiw t0, t0, 0
作为符号扩展,因此编译器可以依赖高位中的内容,但这为许多常见模式(如比较+分支)添加了额外的指令。在这里做出的正确 ISA 设计决定是使用一组不同的 W 指令,例如“比较低 32 位和分支”。如果你运行这些数字,你最终会得到大约相同数量的附加指令(分支和设置,而不是加、减和移位)。问题是分支指令在编码空间方面要昂贵得多,因为它们具有更长的偏移量。由于编码空间被认为是 RISC-V 中的重要资源,当没有明显的性能优势时,我们倾向于选择节省更多编码空间的设计决策。在这种情况下,有'
这里有一个二阶设计决策:规范表示是符号扩展还是零扩展?这里有一个权衡:符号扩展导致更快的软件(使用相同数量的编码空间),但更复杂的硬件。具体来说,常见的 C 片段
long func_pos();
long func_neg();
long neg_or_pos(int a) {
if (a > 0) return func_pos();
return func_neg();
}
使用符号扩展时编译非常有效
neg_or_pos:
bgtz a0,.L4
tail func_neg
.L4:
tail func_pos
但是在使用零扩展名时速度较慢(再次假设我们不愿意在字大小的比较+分支指令上浪费大量编码空间)
neg_or_pos:
addiw a0, a0, 0
bgtz a0,.L4
tail func_neg
.L4:
tail func_pos
当我们进行平衡时,似乎零扩展的软件成本高于符号扩展的硬件成本:对于尽可能小的设计(即微编码实现),您仍然需要算术右移,这样您就不会丢失任何数据路径,并且对于最大可能的设计(即,广泛的乱序内核),代码最终会在分支之前将位改组。奇怪的是,您为符号扩展支付有意义的成本的一个地方是具有短流水线的有序机器:您可以从 ALU 路径中减少 MUX 延迟,这在某些设计中至关重要。实际上,在许多其他地方,符号扩展是正确的决定,因此仅更改这一点不会导致删除该数据路径。