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