在从英特尔文档中摘录的下表中,我们为操作码E8 cw 和E8 cd 提供了位移与下一条指令相关的信息。
为什么下一条指令?为什么位移不相对于call
指令本身?
TL:DR:无论如何,您都可以在解码期间找到指令的结尾,并设置下一条指令的解码。尽管有些CPU做出相对于下一条指令的结束之类的选择(对于ARM PC相对内存寻址),但CPU相对于当前指令的 end 进行相对寻址是很正常的。>
请参见Does Program Counter hold current address or the address of the next instruction?
x86机器代码设计在80年代末与8086紧密相关,但在扩展ISA时需要重新设计的东西(例如32/64位ModRM + SIB寻址模式)。
原始的8086顺序解码的指令字节(不必一次解码整个指令),并且前缀字节数或总指令长度没有上限。
我认为 8086避免了曾经需要保存指令的起始地址,即使是例外情况也是如此。例如,在现代的x86 #DE
(除法异常)上,推送错误指令的地址。但是on 8086 the exception frame has the address of the next instruction。
8086甚至有一个“错误”(或已记录的设计缺陷),其中在执行cs rep movsb
时遇到的中断(例如)将最终前缀的地址作为异常返回地址,从而使段覆盖rep
字符串指令在启用中断的情况下基本上不可用。 (因为执行将在没有rep
或没有段替代的情况下恢复执行,以您为先)。参见x86 Program Counter abstracted from microarchitecture?和评论。
8086完成对call
指令的解码后,它不知道从哪里开始。它唯一的参考点是call
指令的结尾。因此,如果他们想在硬件中进行这种优化(不将解码起始地址保留在任何地方),他们甚至没有一个选择。尽管从理论上讲,他们可以使用E8 call
操作码的地址(在任何前缀之后)作为锚点,但这可能需要额外的加法器或额外的硬件来分别记录。
获取/解码已经必须在解码过程中找到指令的末尾(同时确定它是call
或jmp
),因此指令的末尾/下一个地址-指令已经在内部可用。 call
甚至必须将该值作为返回地址推入堆栈。
流水线RISC或完全未流水线的CPU也将使用该下一条指令地址从内存或I-cache提取下一条指令。但是实际上,8086预取与异步到一个小的预取缓冲区中。机器代码格式主要是在实现设计之前就在纸上进行设计的,因此,使这种与指令末尾相关的事情的常见原因可能是架构师考虑的。
对于许多ISA来说,相对于指令的结尾进行分支是一种常见的设计选择。
只需重申一下,我只讲8086(内部与现代x86完全不同)的原因是它是 first 代,并且理解它有助于解释一些机器代码设计决策。 (例如,为什么x86在单字节xchg [e/r]ax,reg
上花费8个操作码:因为8086没有movsx
或2运算符imul
,并且需要AX来满足很多需求。代码大小是性能的主要瓶颈。)
现代的x86只是跟踪每个指令的地址,并在解码call rel32
时可以使用该地址。没什么大不了的。 Why do x86 jump/call instructions use relative displacements instead of absolute destinations?