在从英特尔文档中提取的下表中,我们有操作码 E8 cw和 E8 cd的位移是相对于下一条指令的。
为什么下一个指令?为什么位移不是相对于call
指令本身?
TL:DR:无论如何,您在解码期间发现指令结束,并设置下一条指令的解码。CPU 相对于当前指令的结尾进行相对寻址是非常正常的,尽管一些 CPU 会做出不同的选择,比如相对于下一条指令的结尾(对于 ARM PC 相对内存寻址)。
x86 机器代码设计是在 70 年代后期与 8086 形成的,除了在扩展 ISA 时重新设计的东西(如 32/64 位 ModRM+SIB 寻址模式)。
原始的 8086 解码指令字节顺序(不一定是一次完整的指令),并且对前缀字节数或总指令长度没有上限。
我认为8086 避免了需要保存指令的起始地址,即使是异常也是如此。例如,在现代 x86 上#DE
(除法异常)推送错误指令的地址。但在 8086 上,异常帧具有下一条指令的地址。
cs rep movsb
8086 甚至有一个“错误”(或记录在案的设计缺陷),其中(例如)执行期间到达的中断将最终前缀的地址作为异常返回地址推送,使得rep
-string 指令上的段覆盖在启用中断的情况下基本上无法使用. (因为执行将在没有rep
或没有段覆盖的情况下恢复,无论你先放哪个)。看到从微架构抽象的 x86 程序计数器?和评论。
当 8086 完成对call
指令的解码时,它不知道它从哪里开始。它唯一的参考点是call
指令的结尾。 因此,如果他们想在硬件中进行优化(不在任何地方保留解码起始地址),他们甚至别无选择。尽管理论上他们可以使用E8 call
操作码的地址(在任何前缀之后)作为锚点,但这可能需要额外的加法器或额外的硬件来单独记录。
获取/解码已经必须在解码期间找到指令的结尾(同时确定它是 a call
or jmp
),因此指令结尾/下一条指令的地址已经在内部可用。 call
甚至必须将该值作为返回地址压入堆栈。
流水线 RISC 或完全非流水线 CPU 也将使用该下一条指令地址从内存或 I-cache 中获取下一条指令。但实际上 8086 预取是异步进入一个小的预取缓冲区的。机器代码格式主要是在设计实现之前在纸上设计的,所以这个使事情与指令结尾相关的常见原因可能是架构师的想法。
对于许多 ISA 来说,相对于指令的结尾进行分支是一种常见的设计选择。
重申一下,我只谈论 8086(在内部与现代 x86非常不同)的原因是它是第一代,理解它有助于解释一些机器代码设计决策。(例如,为什么 x86 在单字节上花费 8 个操作码xchg [e/r]ax, reg
:因为 8086 没有movsx
or 2-operand imul
,并且需要或想要 AX 来处理很多东西。而且代码大小是性能的主要瓶颈。)
现代 x86 只是跟踪每条指令的地址,并且可以在解码时使用它call rel32
。没有大碍。 为什么 x86 跳转/调用指令使用相对位移而不是绝对目的地?