使用可变长度指令,计算机如何知道正在提取的指令的长度?

10
在不是所有指令长度都一样的架构中,计算机如何知道要读取多少个字节作为一条指令呢?例如在Intel IA-32中,有些指令是4个字节,有些是8个字节, 那么它如何知道是否读取4个或8个字节呢?是这样的,第一条在计算机开机时被读取的指令具有已知的大小,并且每个指令都包含下一条指令的大小。

3
硬件读取整个缓存行的字节,然后CPU逐字节解码(至少原则上是这样)。 - 500 - Internal Server Error
@500-服务器内部错误 这句话有什么意思吗? - Celeritas
3个回答

14
首先,处理器不需要知道要获取多少字节,它可以获取足够提供典型或平均指令长度的目标吞吐量的方便数量的字节。任何额外的字节都可以放置在缓冲区中,以便在下一组要解码的字节中使用。在提取相对于支持的指令解码宽度甚至相对于管道后面部分的宽度时存在权衡。提取比平均值更多的字节可以减少指令长度和与已采取控制流指令相关的有效提取带宽的可变性的影响。
(采取的控制流指令可能会引入一个提取气泡,如果[预测的]目标直到下一次提取之后的一个周期才可用,并且通过目标降低了有效提取带宽,这些目标与指令提取不太对齐。例如,如果指令提取是16字节对齐的——这在高性能x86上很常见——将目标定位在块中的第16个[最后一个]字节的采取分支将导致实际上只有一个字节的代码被提取,因为其他15个字节被丢弃。)
即使对于固定长度的指令,每个周期获取多个指令也会引入类似的问题。一些实现(例如MIPS R10000)会获取尽可能多的指令,即使它们没有对齐,只要指令组不跨越缓存行边界。(我记得有一个RISC实现使用了两个Icache标签库来允许获取跨越缓存块,但不跨越页面的指令。)其他实现(例如POWER4)即使分支指向这样一个块中的最后一条指令,也会获取对齐的代码块。(对于POWER4,使用包含8条指令的32字节块,但最多只能在一个周期内解码5条指令。这种超额的获取宽度可以利用节省能量,在不执行获取的周期中给出备用Icache周期以填充缓存块,而只有一个读/写端口连接到Icache。)

为了每个周期解码多条指令,实际上有两种策略:并行进行推测性解码或等待长度被确定并使用该信息将指令流解析成单独的指令。对于像IBM的zArchitecture(S/360后代)这样的ISA,长度在16位包中是通过第一个包中的两个位轻松确定的,因此等待长度被确定更加合理。(RISC V稍微复杂的长度指示机制仍然对非推测性解码友好。) 对于类似于microMIPS或Thumb2的编码,它们只有两个由主操作码确定的长度,而不同长度指令的编码差异显著,因此可能更倾向于使用非推测性解码,特别是考虑到可能窄的解码和注重能效,尽管在小型解码宽度下进行一些推测也是合理的。

对于x86,AMD用的一种策略是在指令缓存中使用标记位来指示哪个字节结束了一条指令,以避免过多的解码能量消耗。有了这些标记位,可以很容易地找到每条指令的起始位置。这种技术的缺点是会增加指令缓存未命中的延迟(需要预解码指令),并且仍然需要解码器检查长度是否正确(例如,在跳转到先前为指令中间的位置时)。
Intel似乎更喜欢使用推测性并行解码方法。由于要解码的块中之前指令的长度只需经过短暂的延迟即可得知,第二个及之后的解码器可能不需要完全解码所有起始点的指令。
由于x86指令可能相对复杂,通常也存在解码模板约束,并且至少有一个早期设计限制了可以使用的前缀数量,同时保持完整的解码带宽。例如,Haswell将第二到第四条指令解码限制为仅产生一个微操作,而第一条指令可以解码为最多四个微操作(使用微代码引擎进行更长的微操作序列)。基本上,这是针对常见情况(相对简单的指令)的优化,以牺牲不太常见的情况为代价。
在最近的性能导向的x86设计中,英特尔使用了一个µop缓存,它以解码格式存储指令,避免了模板和获取宽度限制,并减少了与解码相关的能量消耗。

8
每条指令的前几个字节表示其长度。如果事情很简单,第一个字节将指示长度,但是有一些前缀表明下一个字节是真正的指令,另外还有包含指令操作数的可变长度后缀。
真正的问题是,由于现代乱序处理器每个周期解码3或4条指令,它如何知道第2、第3、...条指令从哪里开始?
答案是,它在当前16字节的代码行中并行地解码所有可能的起始点,以暴力方式进行。我相当确定这个评论/猜测的来源是Agner Fog,但我找不到参考资料。我搜索了“Agner Fog instruction decoding suspect”,但显然他花时间怀疑与指令解码相关的事情。

所有的个人计算机架构都通过解码第一个字节来执行此操作吗?这是一个标准吗?考虑到字节的大小从技术上讲并没有标准,我觉得这很难相信。 - Celeritas
2
@Celeritas,“PC”是什么意思?(还有“standard”和“technically”?)IA-32和x86-64已经相当标准化了。其他架构做事情的方式不同,但如果你要有一个非IA32-non-x86-64架构,那么最好有一个合理的固定长度指令,比如每个指令32位。 - Pascal Cuoq

5

在Pascal的回答上进行扩展,在x86架构上,第一个字节指示正在解码的指令属于哪个类别:

  • 1字节长度,这意味着它已经被读取并且可以进一步处理,

  • 1字节操作码和几个字节(所谓的ModRMSIB字节),用于指示后面跟随的操作数(寄存器、内存地址)及其操作数。

  • 指令前缀,其中:

    • 修改指令的含义(重复-REP,锁定语义-LOCK
    • 表示下一个字节编码一个在原始8086 CPU的后续迭代中引入的指令,以将其操作数的大小扩展到32或64位,或完全重新定义操作码的含义。
此外,根据CPU运行的模式,某些前缀可能有效或无效:例如,REXVEX前缀分别用于实现64位和矢量指令,但仅在64位模式下解释为此类指令。由于其格式,REX涵盖了原始指令集中大量现有指令,这些指令在64位中不再可用(我想VEX前缀也是如此,尽管我对此一无所知)。它的字段指示以下指令操作数大小,或访问仅在64位上可用的额外寄存器(R8R15XMM8XMM15)。
如果您研究操作码内部模式,您会注意到某些位始终指示指令属于哪个类别,从而导致相对较快的解码。

VAX 是另一种体系结构(从70年代末到80年代末很流行),采用类似的原理支持可变长度指令。对于其最初的迭代版本,指令可能是按顺序解码的,因此一个指令的结束指示着下一个字节上的新指令的开始。正如您所知,制造这些产品的公司还生产了它的极端相反,即 RISC Alpha CPU,它成为了当时最快的CPU之一(如果不是最快的),具有固定长度的指令,这显然是为了应对当时蓬勃发展的流水线、超标量技术的要求而做出的选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接