为什么 JALR 对偏移量的 LSB 进行编码?

Why does JALR encode the LSB of the offset?

我们知道jal指定了一个21位的偏移量。但是,它不编码 21 位偏移量,而是编码 20 位偏移量。原因是地址的最低有效位始终为零,因为最小可能的RISC-V指令是2个字节,所以该位未编码在指令中。

通过以这种方式编码偏移量,它可以提供 ±1MiB 的跳跃范围。如果 jal 确实对 LSB 进行了编码,它只会提供 ±512KiB 的跳跃范围。

但是,指定 12 位偏移量的 jalr 指令确实对 LSB 进行了编码。这将跳跃范围减小到 ±2kiB(而不是 ±4kiB)。我知道 jalr 使用 I 型格式,与 addi 相同,并且必须为这种指令编码立即数的 LSB。但是,我看不出为什么必须将最低有效位编码为 jalr.

JALR 用于两个相对不同的目的:

  • 间接分支,例如
    • 函数return
    • 间接函数调用(例如函数指针;vtables/virtual 分派),以及
  • mid-far 分支(在两个指令序列中,具有 32 位 pc 相对范围)。

对于前者,间接分支,立即数始终为 0,也就是说实际上根本没有使用立即数!

对于后者,该指令与AUIPC结合使用,形成pc相对寻址的高20位——然后JALR结合形成低12位-bits,相对于 32 位的总 pc 相对偏移量。

但是,AUIPC 既用于远分支,也用于与 pc 相关的数据访问。因此,它们都共享 12 位偏移量——load/store 使用其 12 位立即数,而 JALR 也使用 12 位立即数字段,就像加载和存储一样。设计师选择共享 AUIPC 而不是为这两种用途(从代码到代码的引用与从代码到数据的引用)提供两种不同的 AUIPC

综上所述,JALR的取值范围多半不重要,只要能补足AUIPC的20位即可。当然还有其他方法,但这确实具有重用和只需要一个 AUIPC 指令的优点。

基本原理已在 RISC-V spec 中说明:

Note that the JALR instruction does not treat the 12-bit immediate as multiples of 2 bytes, unlike the conditional branch instructions. This avoids one more immediate format in hardware. In practice, most uses of JALR will have either a zero immediate or be paired with a LUI or AUIPC, so the slight reduction in range is not significant.

Clearing the least-significant bit when calculating the JALR target address both simplifies the hardware slightly and allows the low bit of function pointers to be used to store auxiliary information. Although there is potentially a slight loss of error checking in this case, in practice jumps to an incorrect instruction address will usually quickly raise an exception.