编译器优化导致的推测性错误是如何在幕后实现的?
How is speculative fault due to compiler optimization implemented under the hood?
此问题是 上的后续问题。
考虑以下代码。
if (*p && *q) {
/* do something */
}
现在根据 的讨论(尤其是 David Schwartz 的评论和回答),符合标准的 C 编译器的优化器可以发出 CPU 访问 [=11] 的指令=] 在 *p
之前,同时仍然保持使用 &&
-运算符建立的序列点的可观察行为。
因此,尽管优化器可能会发出在 *p
之前访问 *q
的代码,但它仍然需要确保可以观察到 *q
的任何副作用(例如分段错误)仅当 *p
为非零时。如果 *p
为零,则不应观察到由于 *q
引起的故障,即由于 *q
首先在 CPU 上执行,因此会首先发生推测性故障,但一旦 *p
被执行并发现为 0.
,推测性错误将被忽略
我的问题:这个推测性错误是如何在幕后实现的?
如果您在回答这个问题时能进一步说明以下几点,我将不胜感激。
- 据我所知,当 CPU 检测到故障时,它会生成一个内核必须处理的陷阱(要么采取页面交换等恢复操作,要么发出 SIGSEGV 等故障信号以过程)。我说得对吗?
- 因此,如果编译器必须发出代码来执行推测性错误,在我看来,内核和编译器(可能还有 CPU)必须相互合作才能实施推测性错误。编译器如何发出指令告诉内核或 CPU 由于代码产生的错误应该被认为是推测性的?
它是作为正常推测性提取过程的一部分实现的。推测性提取的结果,无论是数值结果还是错误,都是推测性的。当且仅当以后需要时才使用它。
As far as I know, when the CPU detects a fault, it generates a trap, that the kernel must handle (either take recovery action such as page swap, or signal the fault such as SIGSEGV to the process). Am I correct?
以非推测方式执行产生错误的提取的结果是一个陷阱。执行提取的结果推测性地产生错误是推测性陷阱,只有在使用推测性提取的结果时才会实际发生。仔细想想,如果没有这个机制,推测性的抓取是不可能的。
So if the compiler must emit code to perform speculative fault, it appears to me that the kernel and the compiler (and possibly the CPU too) must all cooperate with each other to implement speculative fault. How does the compiler emit instructions that would tell the kernel or the CPU that a fault generated due to the code should be considered speculative?
编译器通过在 *p
的结果测试之后放置 *q
的提取来完成它。这表明 CPU 提取是推测性的,它只能在对 *p
的结果的测试结果已知后才能使用结果。
CPU 可以并且确实会在知道是否需要之前执行 *q
的提取。这几乎是必不可少的,因为提取可能需要缓慢的内核间操作——您不想等待超过需要的时间。因此,现代多核 CPUs 实现了积极的推测性抓取。
这就是现代 CPU 所做的。 (CPU 具有显式推测性提取操作的答案不同。)
在 C 和 C++ 中,您有 "as-if" 规则,这意味着编译器可以做任何它喜欢的事情,只要可观察到的行为是语言所承诺的。
如果编译器为没有内存保护的古老处理器生成代码,读取 *q 将读取 something(未指定的值)而没有任何副作用,那么显然它是允许的读取 *q,甚至交换测试的顺序。正如 any 编译器可以交换 (x > 0 || y > 0) 中的操作数一样,只要 y 具有已定义的值或读取具有未定义值的 y 就没有副作用。
但是你问的是处理器中的推测执行。好吧,处理器 do 在条件分支之后执行指令,然后他们才知道条件分支是否被采用,但他们 100% 确定这不会导致任何 可见 副作用。有 never 任何代码,它都在 CPU 中。如果条件执行做了一些应该产生陷阱的事情,那么 CPU 会一直等到它确定分支是否被采用,然后它要么采用陷阱,要么不采用。您的代码看不到它,甚至 OS 也看不到它。
此问题是
考虑以下代码。
if (*p && *q) {
/* do something */
}
现在根据 *p
之前,同时仍然保持使用 &&
-运算符建立的序列点的可观察行为。
因此,尽管优化器可能会发出在 *p
之前访问 *q
的代码,但它仍然需要确保可以观察到 *q
的任何副作用(例如分段错误)仅当 *p
为非零时。如果 *p
为零,则不应观察到由于 *q
引起的故障,即由于 *q
首先在 CPU 上执行,因此会首先发生推测性故障,但一旦 *p
被执行并发现为 0.
我的问题:这个推测性错误是如何在幕后实现的?
如果您在回答这个问题时能进一步说明以下几点,我将不胜感激。
- 据我所知,当 CPU 检测到故障时,它会生成一个内核必须处理的陷阱(要么采取页面交换等恢复操作,要么发出 SIGSEGV 等故障信号以过程)。我说得对吗?
- 因此,如果编译器必须发出代码来执行推测性错误,在我看来,内核和编译器(可能还有 CPU)必须相互合作才能实施推测性错误。编译器如何发出指令告诉内核或 CPU 由于代码产生的错误应该被认为是推测性的?
它是作为正常推测性提取过程的一部分实现的。推测性提取的结果,无论是数值结果还是错误,都是推测性的。当且仅当以后需要时才使用它。
As far as I know, when the CPU detects a fault, it generates a trap, that the kernel must handle (either take recovery action such as page swap, or signal the fault such as SIGSEGV to the process). Am I correct?
以非推测方式执行产生错误的提取的结果是一个陷阱。执行提取的结果推测性地产生错误是推测性陷阱,只有在使用推测性提取的结果时才会实际发生。仔细想想,如果没有这个机制,推测性的抓取是不可能的。
So if the compiler must emit code to perform speculative fault, it appears to me that the kernel and the compiler (and possibly the CPU too) must all cooperate with each other to implement speculative fault. How does the compiler emit instructions that would tell the kernel or the CPU that a fault generated due to the code should be considered speculative?
编译器通过在 *p
的结果测试之后放置 *q
的提取来完成它。这表明 CPU 提取是推测性的,它只能在对 *p
的结果的测试结果已知后才能使用结果。
CPU 可以并且确实会在知道是否需要之前执行 *q
的提取。这几乎是必不可少的,因为提取可能需要缓慢的内核间操作——您不想等待超过需要的时间。因此,现代多核 CPUs 实现了积极的推测性抓取。
这就是现代 CPU 所做的。 (CPU 具有显式推测性提取操作的答案不同。)
在 C 和 C++ 中,您有 "as-if" 规则,这意味着编译器可以做任何它喜欢的事情,只要可观察到的行为是语言所承诺的。
如果编译器为没有内存保护的古老处理器生成代码,读取 *q 将读取 something(未指定的值)而没有任何副作用,那么显然它是允许的读取 *q,甚至交换测试的顺序。正如 any 编译器可以交换 (x > 0 || y > 0) 中的操作数一样,只要 y 具有已定义的值或读取具有未定义值的 y 就没有副作用。
但是你问的是处理器中的推测执行。好吧,处理器 do 在条件分支之后执行指令,然后他们才知道条件分支是否被采用,但他们 100% 确定这不会导致任何 可见 副作用。有 never 任何代码,它都在 CPU 中。如果条件执行做了一些应该产生陷阱的事情,那么 CPU 会一直等到它确定分支是否被采用,然后它要么采用陷阱,要么不采用。您的代码看不到它,甚至 OS 也看不到它。