我将用 MIPS 而不是 x86 来回答这个问题,因为 (1) MIPS 和 x86 在这方面有相似之处,并且因为 (2) RISC V 是由 Patterson 等人开发的,经过数十年的 MIPS 经验. 我觉得他们书中的这些陈述在这个比较中得到了最好的理解,因为 x86 和 MIPS 都对相对于指令结尾的分支偏移进行编码(MIPS 中的 pc+4)。
在 MIPS 和 x86 中,PC 相对寻址模式仅在早期 ISA 版本的分支中发现。后来的版本增加了 PC 相对地址计算(例如 MIPSauipc
或 x86-64 的 LEA 或加载/存储的 RIP 相对寻址模式)。这些都是相互一致的:偏移量是相对于(过去的)指令结尾(即下一条指令开始)编码的——而正如你所注意到的,在 RISC V 中,编码的分支偏移量(和 auipc , etc..) 是相对于指令的开始。
这样做的价值在于它从某些数据路径中移除了一个加法器,有时这些数据路径中的一个可能位于关键路径上,因此对于某些实现而言,数据路径的这种微小缩短意味着更高的时钟速率。
(当然,RISC V 仍然需要为 pc-next 生成指令 + 4 和调用指令的返回地址,但这在关键路径上要少得多。请注意,在下图中,都没有显示 pc+4 的捕获作为退货地址。)
让我们比较一下硬件框图:
MIPS 数据路径(简化)
RISC V 数据路径(简化)
您可以在 RISC V 数据路径图中看到标记为 #5 的线(红色,就在控制椭圆的上方)绕过了加法器(#4,它将 4 加到 pc 中以用于 pc-next)。
图表的归属
为什么 x86 / MIPS 在其初始版本中做出不同的选择?
当然,我不能肯定地说。在我看来,有一个选择要做,而且对于最早的实现来说根本不重要,所以他们可能甚至没有意识到潜在的问题。无论如何,几乎每条指令都需要计算下一条指令,所以这似乎是合乎逻辑的选择。
充其量,他们可能已经节省了一些电线,因为其他指令(例如调用)确实需要 pc-next 并且不一定需要 pc+0 。
对先前处理器的检查可能表明这正是当时的做法,因此这可能更多是对现有方法的继承,而不是设计选择。
8086 没有流水线化(指令预取缓冲区除外),可变长度解码在开始执行之前已经找到指令的结尾。
经过多年的事后看来,这个数据路径问题现在在 RISC V 中得到解决。
我怀疑他们对此做出了同样程度的有意识的决定,例如,对于分支延迟时隙 (MIPS)。
根据评论中的讨论,8086 可能没有任何推送指令起始地址的异常。与后来的 x86 模型不同,除法异常将指令地址推到 div/idiv 之后。而在 8086 中,interrupt-resume after cs rep movsb
(或其他字符串指令)推送最后一个前缀的地址,而不是包括多个前缀的整个指令。此“错误”记录在Intel 的 8086 手册(扫描的 PDF)中。所以很有可能8086真的没有记录指令的起始地址或长度,只记录了开始执行前解码完成的地址。这至少由 286 修复,也许是 186,但适用于所有 8086 / 8088 CPU。
MIPS 从一开始就有虚拟内存,所以它确实需要能够记录错误指令的地址,以便在异常返回后重新运行。此外,软件 TLB 未命中处理还需要重新运行错误指令。但是异常很慢并且无论如何都会刷新管道,并且直到获取之后才检测到,所以无论如何可能都需要一些计算。