(也许不是一个完整的答案,但是当@BeeOnRope 发布答案时,我已经写了一些。无论如何发布这个以获得更多链接和技术细节,以防有人好奇。)
一切都是投机的,直到它退休并成为非投机的,肯定发生的,架构状态的一部分。
例如,任何负载都可能因地址错误而出现故障,任何负载都可能div
因被零除而陷入困境。另请参阅乱序执行与推测执行 当Skylake CPU 错误预测分支时会发生什么?提到分支错误预测是特别处理的,因为它们预计会很频繁。快速恢复可以在错误预测的分支达到退休之前开始,这与例如故障负载的行为不同。(这就是为什么 Meltdown 可以被利用的部分原因。)
因此,即使是“常规”指令在提交之前也是推测性地执行的,它们之间的唯一区别是人为的区别,而不是计算机制造的区别?那么,我假设 CPU 存储了多个可能的回滚点?例如,如果我有可能导致页面错误的加载指令或只是使用过时的值,在条件分支中,CPU 会识别这些指令和场景并为每个指令和场景保存一个状态?我觉得我理解错了,因为这可能会导致大量存储寄存器状态和复杂的依赖关系。
退休状态始终是一致的,因此您始终可以回滚到那里并丢弃所有正在进行的工作,例如,如果外部中断到达,您想要处理它,而无需等待一连串缓存未命中加载全部执行。 当中断发生时,流水线中的指令会发生什么?
这种跟踪基本上是免费发生的,或者是您无论如何都需要做的事情才能检测到哪条指令出错,而不仅仅是某处出现问题。(这称为“精确例外”)
人类可以有用地做出的真正区别是在执行非错误案例期间很有可能出错的推测。如果您的代码得到一个错误的指针,那么它的执行方式并不重要;它会出现页面错误,与本地 OoO 执行详细信息相比,这将非常慢。
您说的是现代无序 (OoO)执行(不仅仅是获取)CPU,例如现代 Intel 或 AMD x86、高端 ARM、MIPS r10000 等。
前端是有序的(沿着预测路径进行推测),从无序后端到非推测性退休状态的提交(也称为退休)也是如此。(又名已知良好的建筑状态)。
CPU 使用两种主要结构来跟踪后端的指令(或在 x86 上,uops = 部分指令)。前端的最后阶段(在获取/解码之后)分配/重命名指令并将它们一次添加到这两个结构中。
- RS = 预留站 = 调度器:尚未执行的指令,等待执行单元。RS 跟踪依赖关系并将最旧的就绪微指令发送到就绪的执行单元。
ROB = ReOrder Buffer:尚未退役的指令。指令按顺序进入和离开,因此它可以只是一个循环缓冲区。
包括一个标志,用于将每个条目标记为已执行或未执行,一旦 RS 将其发送到报告成功的执行单元就设置。ROB 中所有已完成执行位设置的最旧指令都可以“退出”。
还包括一个标志,指示“如果达到退休则有故障”。例如,这避免了花费时间处理来自错误执行路径上的加载指令的页面错误(很可能有指向未映射页面的指针)。要么在分支错误预测的阴影下,要么就在另一条本应首先出现故障但 OoO exec 稍后处理的指令之后(按程序顺序)。
(我还将寄存器重命名到一个大型物理寄存器文件中。这就是“重命名”部分。分配包括选择指令将使用的执行端口,以及为内存指令保留加载或存储缓冲区条目。)
(还有一个存储缓冲区;存储不直接写入 L1d 缓存,它们写入存储缓冲区。这使得可以推测性执行存储并仍然回滚,而不会使它们对其他内核可见。它还解耦了缓存-未执行存储。一旦存储指令退出,存储缓冲区条目“毕业”并有资格提交到 L1d 高速缓存,一旦 MESI 获得对高速缓存行的独占访问权,并且一旦满足内存排序规则。)
执行单元检测一条指令是否应该出错,或者是否被错误推测并应该回滚,但在指令达到退休之前不一定要采取行动。
有序退出是在 OoO exec 之后恢复程序顺序的步骤,包括错误推测异常的情况。
术语:当指令从前端发送到 ROB + RS 时,英特尔将其称为“问题”。其他计算机体系结构的人通常称之为“调度”。
将微指令从 RS 发送到执行单元被英特尔称为“调度”,其他人称为“发布”。