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