【问题标题】:With variable length instructions how does the computer know the length of the instruction being fetched? [duplicate]使用可变长度指令,计算机如何知道正在获取的指令的长度? [复制]
【发布时间】:2014-08-07 19:03:05
【问题描述】:

在并非所有指令的长度都相同的架构中,计算机如何知道一条指令要读取多少内容?例如在 Intel IA-32 中,有些指令是 4 个字节,有些是 8 个字节,那么它如何知道是读取 4 个字节还是 8 个字节呢?是不是机器上电时第一条红色指令的大小是已知的,每条指令都包含下一条的大小?

【问题讨论】:

  • 硬件读取整个缓存行的字节,然后 CPU 一次解码一个字节(至少在原则上)。
  • @500-InternalServerError 怎么说?

标签: assembly cpu-architecture


【解决方案1】:

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

  • 1字节长,表示已经被读取,可以进一步处理,

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

  • 指令前缀,其中:

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

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

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

VAX 是另一种架构(流行于 70 年代末到 80 年代后期),它基于类似的原理运行可变长度指令。对于它的第一次迭代,指令可能是按顺序解码的,因此一条指令的结尾表示下一个字节的新指令的开始。正如您可能知道的那样,制造这些的公司也生产了其相反的RISCAlpha CPU,它成为当时(如果不是)最快的 CPU 之一,具有固定长度的指令,肯定是在对当时蓬勃发展的流水线、超标量技术的需求做出反应。

【讨论】:

    【解决方案2】:

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

    (如果 [predicted] 目标在下一次取指之后的一个周期之前不可用,则采取的控制流指令可能会引入取指气泡,并减少有效取指带宽与目标对齐程度低于指令取指。例如,如果取指是 16 字节对齐的——这在高性能 x86 中很常见——一个以块中第 16 个 [最后一个] 字节为目标的分支将导致实际上只获取一个字节的代码,而其他 15 个字节被丢弃。)

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

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

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

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

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

    在最近的以性能为导向的 x86 设计中,英特尔使用了一个 µop 缓存,它以解码格式存储指令,避免了模板和提取宽度限制,并减少了与解码相关的能源消耗。

    【讨论】:

      【解决方案3】:

      每条指令的第一个字节表示它的长度。如果事情很简单,第一个字节表示长度,但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 条、第 3 条……指令的开始位置?

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

      【讨论】:

      • 所有 PC 架构都通过解码第一个字节来做到这一点吗?他们是标准吗?考虑到字节的大小在技术上不是标准,我觉得这很难相信。
      • @Celeritas 你说的“PC”是什么意思? (和“标准”?和“技术上”?)IA-32 和 x86-64 非常标准化。其他架构做事不同,但如果您要使用非 IA32-non-x86-64 架构,您最好有一个合理的架构,其中包含固定长度的指令,例如,每个 32 位。
      猜你喜欢
      • 2012-01-02
      • 1970-01-01
      • 2012-06-27
      • 2020-08-04
      • 2011-06-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多