如何在硬件中断之前处理分支预测错误
How are branch mispredictions handled before a hardware interrupt
硬件中断发生在特定向量(未屏蔽),CPU检查IF标志并将RFLAGS、CS和RIP压入堆栈,同时后端仍有指令完成,其中之一这些指令的分支预测结果是错误的。通常管道会被刷新并且前端开始从正确的地址获取,但在这种情况下正在进行中断。
When an interrupt occurs, what happens to instructions in the pipeline?
我已经阅读了这篇文章,显然一个解决方案是立即从管道中清除所有内容,这样就不会发生这种情况,然后生成指令将 RFLAGS、CS、RIP 推送到内核堆栈的位置技术支持服务;然而,问题来了,它如何知道与最近的架构状态关联的 (CS:)RIP,以便能够将其推入堆栈(假定前端 RIP 现在处于领先地位)。这类似于 port0 上的 take 分支执行单元如何知道当 take 预测结果错误时应该获取什么的 (CS:)RIP 的问题——是编码到指令中的地址以及预言?当您想到陷阱/异常时会出现同样的问题,CPU 需要将当前指令(错误)或下一条指令(陷阱)的地址推送到内核堆栈,但它如何计算出该指令在流水线中途时的地址——这让我相信地址必须编码到指令中并使用长度信息计算出来,这可能全部在预解码阶段完成..
通常,ReOrder Buffer (ROB) 中的每个条目都有一个字段,用于存储有关指令地址的足够信息,以明确地重建整个指令地址。将每条指令的完整地址存储在 ROB 中可能成本太高。对于尚未分配的指令(即尚未通过流水线的分配阶段),它们需要至少在到达分配阶段之前随身携带此信息。
如果中断和分支预测错误同时发生,处理器可能会选择为中断提供服务。在这种情况下,所有位于错误预测路径上的指令都需要被清除。处理器也可以选择刷新在正确路径上但尚未退休的其他指令。所有这些指令都在 ROB 中,并且它们的指令地址是已知的。对于每个推测的分支,都有一个标记来标识该推测路径上的所有指令,并且该路径上的所有指令都用它标记。如果有另一个后来推测的分支,则使用另一个标签,但它也相对于前一个标签排序。使用这些标签,处理器可以准确地确定当任何推测的分支被证明不正确时要清除哪些指令。这是对应的分支指令在分支执行单元中执行完毕后确定的。分支可能会乱序完成执行。当计算出 msipredicted 分支的正确地址时,它会被转发到提取单元和分支预测单元 (BPU)。获取单元使用它从正确的路径中获取指令,BPU 使用它来更新其预测状态。
处理器可以选择退出错误预测的分支指令本身并刷新所有其他后续指令。回收所有重命名寄存器,并保留在分支退出时映射到体系结构寄存器的那些物理寄存器。此时,处理器执行指令保存当前状态,然后开始获取中断处理程序的指令。
CPU 可能会丢弃 ROB 的内容,在服务中断之前回滚到最新的退休状态。
飞行中的分支未命中不会改变这一点。根据 CPU(更旧/更简单),当中断到达时,它可能已经在回滚到退休状态并由于分支未命中而刷新。
正如@Hadi 所说,CPU 可以选择在那时退出分支(中断推送 CS:RIP 指向正确的分支目标),而不是让它成为中断返回后重新执行。
但这只有在分支指令已经准备好退出时才有效:没有比分支更早的指令仍未执行。由于尽早发现分支未命中很重要,我假设分支恢复在执行期间发现错误预测时开始,而不是等到它退休。 (这与其他类型的故障不同:例如 Meltdown 和 L1TF 是基于故障负载 而不是 触发 #PF
故障处理直到它退役,因此 CPU 确定在真正的执行路径上确实存在错误。您不想开始昂贵的管道冲洗,直到您确定它不在错误预测或早期错误的阴影中。)
但是由于分支未命中不会出现异常,因此在我们首先确定分支指令是正确路径的一部分之前,可以提前开始重定向前端。
例如cmp byte [cache_miss_load], 123
/ je
预测错误,但很长时间不会被发现。然后在该错误预测的阴影下,“错误”路径上的 cmp eax, 1
/ je
运行并发现了错误预测。通过快速恢复,被刷新的微指令和 fetch/decode/exec 来自“正确”路径的微指令甚至可以在更早的错误预测被发现之前就开始。
为了保持较低的 IRQ 延迟,CPUs 不倾向于给飞行中的指令额外的退休时间。此外,任何仍在存储缓冲区中保留其数据(尚未提交给 L1d)的退役存储必须在中断处理程序提交任何存储之前提交。但是中断正在序列化(我认为),并且处理程序中的任何 MMIO 或端口 IO 都可能涉及内存屏障或强顺序存储,因此让更多指令退出可能会损害 IRQ 延迟,如果它们涉及存储。 (一旦商店退休,即使它的数据仍在商店缓冲区中,它也肯定需要发生)。
乱序的后端总是知道如何回滚到已知良好的退休状态; ROB 的全部内容总是被认为是推测性的,因为任何加载或存储都可能出错,许多其他指令也可能出错1. 推测过去的分支不是超级特殊的.
分支仅在具有额外跟踪以实现快速恢复(Nehalem 和更新版本中的分支顺序缓冲区)方面比较特殊,因为它们预期在正常期间以不可忽略的频率进行错误预测手术。有关详细信息,请参阅 。特别是大卫·坎特的名言:
Nehalem enhanced the recovery from branch mispredictions, which has been carried over into Sandy Bridge. Once a branch misprediction is discovered, the core is able to restart decoding as soon as the correct path is known, at the same time that the out-of-order machine is clearing out uops from the wrongly speculated path. Previously, the decoding would not resume until the pipeline was fully flushed.
(这个答案故意非常以英特尔为中心,因为你标记了它 intel, not x86。我假设 AMD 做了类似的事情,并且其他 ISA 的大多数乱序 uarches 可能大致相似。除了内存-顺序错误推测在 CPUs 上与较弱的内存模型无关,其中 CPUs 被允许明显地重新排序负载。)
脚注 1:div
或任何 FPU 指令也可以,如果 FP 异常未被屏蔽。并且非正常的 FP 结果可能需要微码辅助来处理,即使 FP 异常像默认情况下一样被屏蔽。
在 Intel CPUs 上,内存顺序的错误推测也可能导致流水线核弹(加载推测性地提前完成,在较早的加载完成之前,但缓存丢失了它之前的行副本x86 内存模型表示负载可以取其值)。
硬件中断发生在特定向量(未屏蔽),CPU检查IF标志并将RFLAGS、CS和RIP压入堆栈,同时后端仍有指令完成,其中之一这些指令的分支预测结果是错误的。通常管道会被刷新并且前端开始从正确的地址获取,但在这种情况下正在进行中断。
When an interrupt occurs, what happens to instructions in the pipeline?
我已经阅读了这篇文章,显然一个解决方案是立即从管道中清除所有内容,这样就不会发生这种情况,然后生成指令将 RFLAGS、CS、RIP 推送到内核堆栈的位置技术支持服务;然而,问题来了,它如何知道与最近的架构状态关联的 (CS:)RIP,以便能够将其推入堆栈(假定前端 RIP 现在处于领先地位)。这类似于 port0 上的 take 分支执行单元如何知道当 take 预测结果错误时应该获取什么的 (CS:)RIP 的问题——是编码到指令中的地址以及预言?当您想到陷阱/异常时会出现同样的问题,CPU 需要将当前指令(错误)或下一条指令(陷阱)的地址推送到内核堆栈,但它如何计算出该指令在流水线中途时的地址——这让我相信地址必须编码到指令中并使用长度信息计算出来,这可能全部在预解码阶段完成..
通常,ReOrder Buffer (ROB) 中的每个条目都有一个字段,用于存储有关指令地址的足够信息,以明确地重建整个指令地址。将每条指令的完整地址存储在 ROB 中可能成本太高。对于尚未分配的指令(即尚未通过流水线的分配阶段),它们需要至少在到达分配阶段之前随身携带此信息。
如果中断和分支预测错误同时发生,处理器可能会选择为中断提供服务。在这种情况下,所有位于错误预测路径上的指令都需要被清除。处理器也可以选择刷新在正确路径上但尚未退休的其他指令。所有这些指令都在 ROB 中,并且它们的指令地址是已知的。对于每个推测的分支,都有一个标记来标识该推测路径上的所有指令,并且该路径上的所有指令都用它标记。如果有另一个后来推测的分支,则使用另一个标签,但它也相对于前一个标签排序。使用这些标签,处理器可以准确地确定当任何推测的分支被证明不正确时要清除哪些指令。这是对应的分支指令在分支执行单元中执行完毕后确定的。分支可能会乱序完成执行。当计算出 msipredicted 分支的正确地址时,它会被转发到提取单元和分支预测单元 (BPU)。获取单元使用它从正确的路径中获取指令,BPU 使用它来更新其预测状态。
处理器可以选择退出错误预测的分支指令本身并刷新所有其他后续指令。回收所有重命名寄存器,并保留在分支退出时映射到体系结构寄存器的那些物理寄存器。此时,处理器执行指令保存当前状态,然后开始获取中断处理程序的指令。
CPU 可能会丢弃 ROB 的内容,在服务中断之前回滚到最新的退休状态。
飞行中的分支未命中不会改变这一点。根据 CPU(更旧/更简单),当中断到达时,它可能已经在回滚到退休状态并由于分支未命中而刷新。
正如@Hadi 所说,CPU 可以选择在那时退出分支(中断推送 CS:RIP 指向正确的分支目标),而不是让它成为中断返回后重新执行。
但这只有在分支指令已经准备好退出时才有效:没有比分支更早的指令仍未执行。由于尽早发现分支未命中很重要,我假设分支恢复在执行期间发现错误预测时开始,而不是等到它退休。 (这与其他类型的故障不同:例如 Meltdown 和 L1TF 是基于故障负载 而不是 触发 #PF
故障处理直到它退役,因此 CPU 确定在真正的执行路径上确实存在错误。您不想开始昂贵的管道冲洗,直到您确定它不在错误预测或早期错误的阴影中。)
但是由于分支未命中不会出现异常,因此在我们首先确定分支指令是正确路径的一部分之前,可以提前开始重定向前端。
例如cmp byte [cache_miss_load], 123
/ je
预测错误,但很长时间不会被发现。然后在该错误预测的阴影下,“错误”路径上的 cmp eax, 1
/ je
运行并发现了错误预测。通过快速恢复,被刷新的微指令和 fetch/decode/exec 来自“正确”路径的微指令甚至可以在更早的错误预测被发现之前就开始。
为了保持较低的 IRQ 延迟,CPUs 不倾向于给飞行中的指令额外的退休时间。此外,任何仍在存储缓冲区中保留其数据(尚未提交给 L1d)的退役存储必须在中断处理程序提交任何存储之前提交。但是中断正在序列化(我认为),并且处理程序中的任何 MMIO 或端口 IO 都可能涉及内存屏障或强顺序存储,因此让更多指令退出可能会损害 IRQ 延迟,如果它们涉及存储。 (一旦商店退休,即使它的数据仍在商店缓冲区中,它也肯定需要发生)。
乱序的后端总是知道如何回滚到已知良好的退休状态; ROB 的全部内容总是被认为是推测性的,因为任何加载或存储都可能出错,许多其他指令也可能出错1. 推测过去的分支不是超级特殊的.
分支仅在具有额外跟踪以实现快速恢复(Nehalem 和更新版本中的分支顺序缓冲区)方面比较特殊,因为它们预期在正常期间以不可忽略的频率进行错误预测手术。有关详细信息,请参阅
Nehalem enhanced the recovery from branch mispredictions, which has been carried over into Sandy Bridge. Once a branch misprediction is discovered, the core is able to restart decoding as soon as the correct path is known, at the same time that the out-of-order machine is clearing out uops from the wrongly speculated path. Previously, the decoding would not resume until the pipeline was fully flushed.
(这个答案故意非常以英特尔为中心,因为你标记了它 intel, not x86。我假设 AMD 做了类似的事情,并且其他 ISA 的大多数乱序 uarches 可能大致相似。除了内存-顺序错误推测在 CPUs 上与较弱的内存模型无关,其中 CPUs 被允许明显地重新排序负载。)
脚注 1:div
或任何 FPU 指令也可以,如果 FP 异常未被屏蔽。并且非正常的 FP 结果可能需要微码辅助来处理,即使 FP 异常像默认情况下一样被屏蔽。
在 Intel CPUs 上,内存顺序的错误推测也可能导致流水线核弹(加载推测性地提前完成,在较早的加载完成之前,但缓存丢失了它之前的行副本x86 内存模型表示负载可以取其值)。