在硬件中断之前,如何处理分支预测失败

4

硬件中断会发生在特定的向量(未屏蔽),CPU检查IF标志并将RFLAGS、CS和RIP推入堆栈,同时后端仍有指令正在完成,其中一个指令的分支预测结果是错误的。通常情况下,流水线会被清空,前端开始从正确的地址获取,但在这种情况下,中断正在进行中。

当中断发生时,流水线中的指令会发生什么?

我已经阅读了这篇文章,显然解决方案是立即清空管道中的所有内容,以避免出现这种情况,然后生成指令将RFLAGS、CS、RIP推送到TSS中内核堆栈的位置;但是,问题是如何知道与最近的体系结构状态相关联的(CS:)RIP,以便能够将其推送到堆栈上(考虑到前端RIP现在已经超前)。这类似于当取预测错误时,端口0上的取分支执行单元如何知道应该获取的(CS:)RIP的问题--地址是否也被编码到指令中以及预测?当您考虑陷阱/异常时,同样的问题会出现,CPU需要将当前指令(故障)或下一条指令(陷阱)的地址推送到内核堆栈,但是当它在管道中间时,如何确定这个指令的地址--这让我相信地址必须被编码到指令中,并且使用长度信息计算,这可能都是在预解码阶段完成的...
2个回答

5

中央处理器很可能会丢弃ROB的内容,回滚到服务中断之前的最新退役状态。

正在执行的分支错误不会改变这一点。根据CPU(旧/简单),当中断到达时,它可能已经在回滚到退役状态和清除流水线的过程中因为分支错误而进行了。

正如@Hadi所说,CPU可以选择在那时退役分支(中断推送指向正确分支目标的CS:RIP),而不是让其在从中断返回后重新执行。

但是,这只有在分支指令已经准备好退役的情况下才有效:没有比该分支更旧的指令尚未执行。由于尽早发现分支错误非常重要,我假设当它在执行期间发现错误预测时,分支恢复就会开始,而不是等待到达退役。 (这与其他类型的故障不同:例如,Meltdown和L1TF基于故障负载触发#PF故障处理,直到它到达退役,因此CPU确信真实执行路径上确实存在故障。在您确定它不在错误预测或更早的故障的阴影之前,不要启动昂贵的管道清除。)
但是,由于分支错误不会引发异常,因此在我们确定分支指令首先属于正确路径之前,可以尽早开始重定向前端。

例如:cmp byte [cache_miss_load],123 / je 错误预测但很长一段时间内不会被发现。然后,在那个错误预测的阴影下,一个在“错误”的路径上运行的 cmp eax,1 / je 被发现也出现了错误预测。通过快速恢复,超过该点的uops将被清除,并且从“正确”路径提取/解码/执行可以在早期的错误预测甚至被发现之前开始。


为了保持中断请求延迟低,CPU不会给已经执行的指令额外时间去完成。此外,任何已经完成但是其数据仍在存储缓冲区的写入操作(还未提交给L1d)都需要在中断处理程序的写操作之前提交。但中断是串行化的(我认为),处理程序中的任何MMIO或端口IO可能涉及内存屏障或强顺序存储,因此如果这些指令涉及写操作,让更多指令完成可能会影响中断请求延迟。(一旦一个写入操作完成,即使它的数据仍在存储缓冲区中,它也绝对需要发生)。
强调:乱序的后端总是知道如何回滚到已知的良好退役状态; ROB的整个内容始终被视为推测性的,因为任何负载或存储都可能出错,许多其他指令也可能出错。超越分支的推断并不是特别特殊的。
分支只有在具有额外的跟踪以进行快速恢复时才是特殊的(Nehalem和更新版本中的Branch Order Buffer),因为它们在正常操作期间预计会发生错误预测的频率是非常高的。有关详细信息,请参见当skylake CPU错误预测分支时会发生什么?尤其是David Kanter的引用:
"Nehalem增强了从分支错误预测中恢复的能力,这一能力已经延续到Sandy Bridge。一旦发现分支错误预测,核心就能够在已知正确路径的同时重新启动解码,同时乱序机器正在清除来自错误推测路径的uops。此前,解码将不会恢复,直到管道完全刷新。"
(这个答案有意地以英特尔为中心,因为您标记了,而不是。我假设AMD也会做类似的事情,其他ISA的乱序uarch大致相似。除了在具有较弱内存模型的CPU上不存在内存顺序错误规范,其中CPU被允许可见地重新排序负载之外。)
注1:如果FP异常未被屏蔽,div或任何FPU指令都可以执行。即使像默认情况下那样屏蔽FP异常,一个非规格化的FP结果也可能需要微码协助处理。
在英特尔CPU上,内存顺序错误也可能导致流水线故障(在早期进行了负载推测,而之前的负载尚未完成,但是高速缓存失去了行的副本,此时x86内存模型表示负载可以获取其值)。

1
英特尔手册V3第11.10节提到,当引发中断或异常时,已退役的存储器会被清空。AMD手册V2第7.5节提到,中断和异常是完全序列化事件。但我认为在英特尔处理器上并不保证这一点(手册中有没有提到呢?)。在AMD和英特尔处理器上,IRET是完全序列化的。 - Hadi Brais
@HadiBrais:这不是我研究过的东西,感谢你的检查。我想知道一些CPU可能甚至在存储提交之前都不会开始运行中断处理程序的指令,所以发现AMD确实是这样的很有趣。也许英特尔也是如此,但措辞并不100%具体。是的,我知道iret,但如果我们直到IRQ处理程序完成工作后才进行序列化,那么它对于中断延迟就不会有太大问题(只有吞吐量成本)。这就是为什么我提到IRQ处理程序存储必须等待缓冲区刷新的原因。 - Peter Cordes
关于Meltdown的评论很好,还有关于存储缓冲区的问题,我之前没有考虑过它们是否会被清除。@HadiBrais 我之前不知道串行指令这个术语,但我认为对于IRET来说,它只是确保在内核模式存储仍在发生时不能切换回用户模式。我还在阅读中。这是很多东西要理解,特别是当你试图想象电路逻辑如何处理所有不同的条件时。 - Lewis Kelsey
@LewisKelsey:在x86上,“序列化”意味着在执行下一条指令之前完全刷新流水线,包括存储缓冲区。就像cpuid一样。有关内存序列化与指令的区别,请参见MFENCE/SFENCE/etc“序列化内存但不执行指令”?。此外,有关屏障与完全序列化的一些提及,请参见一个x86 CPU有多少内存屏障指令? - Peter Cordes

3
总的来说,ReOrder Buffer (ROB)中的每个条目都有一个字段,用于存储关于指令地址的足够信息,以无歧义地重构整个指令地址。对于每个指令在ROB中存储完整地址可能成本过高。尚未分配的指令(即尚未通过流水线分配阶段)需要携带此信息,至少要在到达分配阶段之前保留。
如果同时发生中断和分支预测失败,处理器可能选择优先服务中断。在这种情况下,所有在错误预测路径上的指令都需要被清除。处理器也可以选择清除正确路径上但尚未退役的其他指令。所有这些指令都在ROB中,其指令地址已知。针对每个推测分支,都有一个标记,用于标识该推测路径上的所有指令,并且该路径上的所有指令都被标记。如果有另一个稍后推测的分支,则使用另一个标记,但它也会与先前的标记一起排序。使用这些标记,处理器可以在任何推测分支被确定不正确后确定要清除哪些指令。这是在分支执行单元中对应的分支指令完成执行后确定的。分支可能按顺序完成执行。当计算出msipredicted分支的正确地址时,它将被转发到获取单元和分支预测单元(BPU)。获取单元使用它从正确路径上获取指令,并且BPU使用它来更新其预测状态。
处理器可以选择退役错误预测分支指令并清除所有后续指令。所有重命名寄存器都将被收回,并保留映射到架构寄存器的物理寄存器。在此时刻,处理器执行保存当前状态的指令,然后开始获取中断处理程序的指令。

1
这些用于特定分支之后的推测标签仅存在于新型CPU上,这些CPU具有分支顺序缓冲区,以允许在发现分支错误时进行有效的回滚。我的理解是,在早期的CPU中,当一个错误预测的分支到达退役状态时,会刷新到最后已知的良好退役状态,而不是在错误预测分支之前的uops /指令仍在执行时进行快速恢复。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接