
[英]What's the meaning of “each CPU instruction can manipulate 32 bits of data”?
[英]What is the meaning of the data32 data32 nopw %cs:0x0(%rax,%rax,1) instruction in gcc inline asm?
在为gcc编译器的-O2优化运行一些测试时,我在反汇编代码中观察到以下指令:
data32 data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)
这个指令做了什么?
为了更详细,我试图了解编译器如何使用O2优化优化无用的递归,如下所示:
int foo(void)
{
return foo();
}
int main (void)
{
return foo();
}
上面的代码在没有优化的情况下编译时导致堆栈溢出,但适用于O2优化代码。
我认为使用O2它完全删除了推送函数foo的堆栈,但为什么data32 data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)
需要?
0000000000400480 <foo>:
foo():
400480: eb fe jmp 400480 <foo>
400482: 66 66 66 66 66 2e 0f data32 data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)
400489: 1f 84 00 00 00 00 00
0000000000400490 <main>:
main():
400490: eb fe jmp 400490 <main>
您会看到cpu管道的操作数转发优化。
虽然它是一个空循环,但gcc也试图优化它:-)。
您运行的cpu具有超标量体系结构。 这意味着它有一个管道,并且连续指令的执行的不同阶段是并行发生的。 例如,如果有
mov eax, ebx ;(#1)
mov ecx, edx ;(#2)
然后,当执行#1时,指令#2的加载和解码可能已经发生。
流水线在分支机构的情况下有很大的问题需要解决,即使它们是无条件的。
例如,当jmp
正在解码时,下一条指令已经被预取到流水线中。 但是jmp
改变下一条指令的位置。 在这种情况下,管道需要通过清空和重新填充,并且将丢失许多有价值的cpu周期。
如果管道在这种情况下用no-op填充,看起来这个空循环将运行得更快,尽管它不会被执行。 它实际上是对x86管道的一些不常见功能的优化。
早期的dec alpha甚至可以从这些东西中断行,而空循环必须有很多no-ops。 x86只会慢一些。 这是因为它们必须与intel 8086兼容。
在这里,您可以从管道中的分支指令处理中阅读很多内容。
要回答标题中的问题,说明
data32 data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)
是一个14字节的NOP(无操作)指令,用于填充foo
函数和main
函数之间的间隙,以保持16字节对齐。
x86体系结构具有大量不同大小的不同NOP指令,可用于将填充插入可执行段,以便在CPU最终执行它们时它们不起作用。 然后, 英特尔优化手册包含有关可用作填充的不同长度的推荐NOP编码的信息。
在这种特定情况下,它完全不相关,因为NOP永远不会被执行(或者甚至在无条件跳转之后被解码),因此编译器可以填充它想要的任何随机垃圾。
函数foo()是一个无终止的无限递归。 如果没有优化,gcc会生成正常的子程序调用,包括至少堆叠返回地址。 由于堆栈有限,这将创建堆栈溢出,即_undefined_behaviour_。
如果优化,gcc检测到foo()根本不需要堆栈帧(没有参数或局部变量)。 它还检测到,foo()立即返回调用者(也可能是foo())。 这称为尾部链接:函数末尾的函数调用(即显式/隐式返回)转换为跳转到该函数,因此不需要堆栈。
这仍然是未定义的行为,但这一次,没有观察到任何“坏”。
请记住:undefined包括致命行为以及预期行为(但这只是偶然)。 对不同优化级别表现不同的代码应该始终是regarder错误的。 有一个例外:时间安排。 这不受C语言标准的约束(大多数其他语言都不符合)。
正如其他人所说,data32 ...是非常确定的填充,以获得16字节对齐,这可能是内部指令总线和/或高速缓存行的大小。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.