假设我有如下的伪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指令。因此,我的问题如下:
1.在x86系统上,“call”是否被视为WRITE指令?我假设是这样,因为call会将地址推入堆栈。但我没有找到官方文件正式说明这一点。所以请帮助确认。
1.出于同样的原因,“ret”是否被视为READ指令(因为它从堆栈中弹出地址)?
1.实际上,“ret”指令可以在函数中重新排序吗?例如,在下面的ASM代码中,(3)可以在(2)之前执行吗?这对我来说没有意义,但“ret”不是序列化指令。我在英特尔手册中没有找到任何地方说“ret”不能重新排序。
1.在上面的代码中,(1)是否可以在(4)之前执行?据推测,read指令(1)可以在write指令(4)之前重新排序。“call”指令可能有“jmp”部分,但在推测执行中......因此,我认为这是可能发生的,但我希望更熟悉此问题的人能够确认这一点。
1.在上面的代码中,(5)可以在(2)之前执行吗?如果“ret”被认为是一个READ指令,那么我假设它不可能发生。但是,我再次希望有人能证实这一点。
如果需要func 1()的汇编代码,它应该类似于:
mov %gs:0x24,%eax (1)
orl $0x8,%gs:0x24 (2)
retq (3)
1条答案
按热度按时间cig3rfwq1#
乱序执行可以对任何东西重新排序,但它保留了你的代码按程序顺序执行的假象。OoOE的基本规则是你不能破坏单线程程序。硬件跟踪依赖关系,这样一旦指令的输入和执行单元准备好,指令就可以执行,但它保留了一切按程序顺序发生的假象。
您似乎混淆了单核上的OoOE与加载/存储对其他内核全局可见的顺序。(The store buffer decouples those)
如果有一个线程观察在另一个内核上运行的另一个线程的堆栈内存,那么是的,
call
生成的存储(推送返回地址)将与其他存储一起排序。然而,**当存储因缓存未命中而延迟时,或者当执行长相关性链时,运行此代码的线程中的无序执行实际上可以执行
call
和ret
指令。**多个缓存未命中可以同时进行。内存顺序缓冲区只需确保在较早的存储之后,较晚的存储才真正变得全局可见,以保留x86的内存排序语义。如果你有一个关于硬件重新排序的具体问题,你可能应该发布asm代码,而不是C代码,因为C++ compilers can reorder at compile time based on the C++ memory model,当编译像x86这样的强排序目标时,它不会改变。
另请参阅内存重新排序如何帮助处理器和编译器?(这是一个Java问题,但我的答案不是Java特有的)。
回复:您编辑
这个答案已经假设了您的函数是
noinline
,并且您正在谈论的ASM看起来像您的C,而不是编译器实际上从您的代码生成的ASM。所以
x
实际上是在线程本地存储中,而不是普通的全局int x
。具有X1 M6 N1 X段覆盖的加载仍然是加载。