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

时间:2014-06-17 16:53:12

标签: assembly cpu-architecture

在并非所有指令长度相同的架构中,计算机如何知道一条指令的读取量?例如,在Intel IA-32中,一些指令是4个字节,有些是8个字节,所以它如何知道是读4个还是8个字节?机器开机时第一条指令是红色的,是否已知大小,每条指令都包含下一条指令的大小?

3 个答案:

答案 0 :(得分:9)

首先,处理器不需要知道要获取多少字节,它可以获取足够的方便数量的字节,以提供典型或平均指令长度的目标吞吐量。任何额外的字节都可以放在缓冲区中,以便在下一组要解码的字节中使用。相对于支持的指令解码宽度,甚至相对于管道的后续部分的宽度,在获取的宽度和对齐方面存在权衡。获取比平均值更多的字节可以减少指令长度的可变性和与采用的控制流指令相关的有效获取带宽的影响。

(如果[预测]目标在下一次提取之后的一个周期之前不可用,则采用控制流指令可能会引入一个提取气泡,并且使用比指令提取更少对齐的目标减少有效提取带宽。例如,如果取指令是16字节对齐 - 这对于高性能x86来说是常见的 - 一个针对块中第16个[最后]字节的分支将导致有效地仅取出一个字节的代码,因为其他15个字节被丢弃。)

即使对于固定长度指令,每个周期取出多个指令也会引入类似的问题。一些实现(例如,MIPS R10000)将获取尽可能多的指令,即使它们未对齐,只要指令组不跨越高速缓存行边界即可。 (我似乎记得一个RISC实现了两组Icache标签,允许提取跨越缓存块而不是页面边界。)其他实现(例如,POWER4)将获取对齐的代码块,即使是针对最后一个的分支在这样一个块中的指令。 (对于POWER4,使用了32个字节的块,其中包含8条指令,但每个周期最多可以有5条指令通过解码。这个超额的读取宽度可以用来通过不执行读取的周期来节省能量,并为缓存块填充提供备用Icache周期在错过之后,只有一个读/写端口到Icache。)

为了每个周期解码多个指令,实际上有两种策略:并行推测性解码或等待确定长度,并使用该信息将指令流解析为单独的指令。对于像IBM的zArchitecture(S / 360后代)这样的ISA,16位宗地的长度通常由第一个宗地中的两个位确定,因此等待确定长度更有意义。 (RISC V's稍微复杂一点的长度指示机制对于非推测性解码仍然是友好的。)对于像microMIPS或Thumb2那样的编码,它只有两个长度可由主操作码确定,并且编码不同长度指令实质上是不同的,使用非推测性解码可能是优选的,特别是考虑到可能的窄解码和强调能效,尽管只有两个长度,一些推测在小解码宽度时可能是合理的。

对于x86,AMD使用的避免过多解码能量使用的一种策略是在指令高速缓存中使用标记位来指示哪个字节结束指令。使用这样的标记位,可以很容易地找到每条指令的开始。该技术的缺点在于它增加了指令高速缓存未命中的等待时间(指令必须预先编码)并且仍然需要解码器检查长度是否正确(例如,如果跳转到之前的状态)中间的指示)。

英特尔似乎更喜欢推测性并行解码方法。由于待解码的块中的先前指令的长度仅在适度延迟之后可用,所以第二和后面的解码器可能不需要完全解码所有起始点的指令。

由于x86指令可能相对复杂,因此通常还存在解码模板约束,并且至少一个早期设计限制了在维持完整解码带宽时可以使用的前缀数量。例如,Haswell限制解码的第二至第四指令仅产生一个μop,而第一指令可解码为最多4μs(使用微码引擎具有更长的μop序列)。基本上,这是对常见情况(相对简单的指令)的优化,代价是不太常见的情况。

在最近面向性能的x86设计中,英特尔使用μop缓存,以解码格式存储指令,避免模板和获取宽度限制,并减少与解码相关的能耗。

答案 1 :(得分:7)

每条指令的第一个字节表示其长度。如果事情很简单,第一个字节将表示长度,但是there are prefixes that indicate that the next byte is the real instruction, in addition to variable-length suffixes that contain instruction operands

真正的问题是,由于现代无序处理器每个周期解码3或4条指令,它如何知道第2,3d,...指令的开始位置?

答案是它以并行,强力风格解码当前16字节代码行中的所有可能起点。我很确定这个评论/猜测的来源是Agner Fog,但我无法找到参考。我用谷歌搜索“Agner Fog指令解码嫌疑人”,但显然是he spends his time suspecting things in relation to instruction decoding

答案 2 :(得分:5)

扩展Pascal的答案,在x86 architecture上,第一个字节表示正在解码的指令所属的指令类别:

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

  • 带有更多字节的1字节操作码(所谓的 ModRM SIB 字节),用于指示跟随哪些操作数(寄存器,存储器地址),和他们的操作。

  • 指令前缀,其中:

    • 修改指令的含义,(重复 - REP,锁定语义 - LOCK
    • 表示下一个字节编码在原始8086 cpu的后续迭代中引入的指令,要么将其操作数的大小扩展为32位或64位,要么完全重新定义操作码含义。

此外,根据CPU运行的模式,某些前缀可能有效,也可能无效:例如,引入REXVEX前缀分别实现64位和向量指令,但它们仅在64位模式下被解释为这样。 REX由于其格式,覆盖了原始指令集中的大量现有指令,不能再以64位使用(我认为VEX前缀的工作方式类似,但我不是&# 39;对此一无所知)。其字段指示以下指令操作数大小,或仅在64位(R8R15XMM8XMM15上提供对额外寄存器的访问。)

如果您研究操作码内部模式,您会注意到某些位一致地指示该指令属于哪个类别,从而导致解码速度稍快。

VAX是另一种架构(从70年代末到80年代后期流行),它基于类似的原则运行可变长度指令。对于其第一次迭代,指令可能是顺序解码的,因此指令的结尾指示下一个字节上的新指令的开始。正如您所知,制造这些产品的公司也产生了极性相反的RISC Alpha CPU,它成为当时最快的CPU(如果不是),具有固定长度指令,选择肯定是为了满足当时蓬勃发展的流水线,超标量技术的要求。