assembly 指令顺序是否会发生跨函数调用?

93ze6v8z  于 2022-11-13  发布在  其他
关注(0)|答案(1)|浏览(146)

假设我有如下的伪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)
cig3rfwq

cig3rfwq1#

乱序执行可以对任何东西重新排序,但它保留了你的代码按程序顺序执行的假象。OoOE的基本规则是你不能破坏单线程程序。硬件跟踪依赖关系,这样一旦指令的输入和执行单元准备好,指令就可以执行,但它保留了一切按程序顺序发生的假象。
您似乎混淆了单核上的OoOE与加载/存储对其他内核全局可见的顺序。(The store buffer decouples those
如果有一个线程观察在另一个内核上运行的另一个线程的堆栈内存,那么是的,call生成的存储(推送返回地址)将与其他存储一起排序。
然而,**当存储因缓存未命中而延迟时,或者当执行长相关性链时,运行此代码的线程中的无序执行实际上可以执行callret指令。**多个缓存未命中可以同时进行。内存顺序缓冲区只需确保在较早的存储之后,较晚的存储才真正变得全局可见,以保留x86的内存排序语义。
如果你有一个关于硬件重新排序的具体问题,你可能应该发布asm代码,而不是C代码,因为C++ compilers can reorder at compile time based on the C++ memory model,当编译像x86这样的强排序目标时,它不会改变。
另请参阅内存重新排序如何帮助处理器和编译器?(这是一个Java问题,但我的答案不是Java特有的)。

回复:您编辑

这个答案已经假设了您的函数是noinline,并且您正在谈论的ASM看起来像您的C,而不是编译器实际上从您的代码生成的ASM。

mov    %gs:0x24,%eax          (1)                                                                                                                                                                                                
orl    $0x8,%gs:0x24          (2)                                                                                                                                                                                                
retq                          (3)

所以x实际上是在线程本地存储中,而不是普通的全局int x。具有X1 M6 N1 X段覆盖的加载仍然是加载。

相关问题