假设我有如下的伪C代码:
int x = 0;
int y = 0;
int __attribute__ ((noinline)) func1(void)
{
int prev = x; (1)
x |= FLAG; (2)
return prev; (3)
}
int main(void)
{
int tmp;
...
y = 5; (4)
compiler_mem_barrier();
func1();
compiler_mem_barrier();
tmp = y; (5)
...
}
假设这是一个单线程进程,所以我们不必担心 关于锁。并假设代码在x86系统上运行。我们还假设编译器没有进行任何重新排序。
据我所知,x86系统只能重新排序写入/读取指令 (对于不同位置的旧写入,可以对读取进行重新排序,但是 不要用较旧的写入同一位置)。但这对我来说并不清楚 如果call / ret指令被认为是WRITE / READ 说明。所以这是我的问题:
在x86系统上,是"呼叫"作为WRITE指令处理?我假设是因为调用会将地址推送到堆栈。但我没有找到官方文件正式说明这一点。所以请帮忙确认。
出于同样的原因,是" ret"作为READ指令处理(因为它从堆栈中弹出地址)?
实际上,可以" ret"指令在函数内重新排序。例如,是否可以(3)在下面的ASM代码中执行(2)?这对我来说没有意义,但是" ret"不是序列化指令。我在“英特尔手册”中没有找到任何地方说" ret"不能重新订购。
在上面的代码中,(1)可以在(4)之前执行吗?据推测,读指令(1)可以在写指令(4)之前重新排序。 "电话"指令可能有一个" jmp"部分,但有投机执行....所以我觉得它可能发生,但我希望更熟悉这个问题的人可以证实这一点。
在上面的代码中,(5)可以在(2)之前执行吗?如果" ret"被认为是一个READ指令,然后我认为它不会发生。但同样,我希望有人可以证实这一点。
如果需要func1()的汇编代码,它应该类似于:
mov %gs:0x24,%eax (1)
orl $0x8,%gs:0x24 (2)
retq (3)
请帮忙。谢谢!
答案 0 :(得分:4)
乱序执行可以重新排序任何内容,但它保留了您的代码按程序顺序执行的错觉。 OoOE的基本规则是你不要破坏单线程程序。硬件跟踪依赖关系,因此指令可以在输入和执行单元准备就绪后立即执行,但保留了一切都按程序顺序发生的错觉。
您似乎在单个核心上混淆了OoOE,其中加载/存储对其他核心全局可见。
如果你有一个线程观察另一个核心上运行的另一个线程的堆栈内存,那么是的,call
生成的商店(推送一个返回地址)将与其他商店一起订购。
然而,运行此代码的线程中的无序执行实际上可以执行call
和ret
指令,而存储在缓存未命中时延迟,或者长时间依赖链正在执行。多个缓存未命中可以立即进行。内存顺序缓冲区必须确保以后的存储实际上在早期存储之前不会全局可见,以保留x86的内存排序语义。
如果您有关于硬件重新排序的具体问题,您应该发布asm代码,而不是C代码,因为C++ compilers can reorder at compile time based on the C++ memory model,在编译像x86这样的强排序目标时不会发生变化。< / p>
另请参阅How does memory reordering help processors and compilers?(一个Java问题,但我的答案不是特定于Java的。)
re:您的修改
这个答案已经假定你的函数是noinline
,并且你在谈论看起来像你的C的ASM,而不是编译器实际会从你的代码生成的。
mov %gs:0x24,%eax (1)
orl $0x8,%gs:0x24 (2)
retq (3)
所以x
实际上是在线程本地存储中,而不是普通的全局int x
。但是,这对于乱序执行并不重要;具有%gs
段覆盖的负载仍然是负载。