【问题标题】:Why is icc generating weird assembly for a simple main?为什么 icc 会为一个简单的 main 生成奇怪的程序集?
【发布时间】:2018-09-03 00:22:12
【问题描述】:

我有一个简单的program

int main()
{
    return 2*7;
}

打开优化的 GCC 和 clang 都可以生成 2 条指令二进制文件,但 icc 会给出奇怪的输出。

     push      rbp                                           #2.1
     mov       rbp, rsp                                      #2.1
     and       rsp, -128                                     #2.1
     sub       rsp, 128                                      #2.1
     xor       esi, esi                                      #2.1
     mov       edi, 3                                        #2.1
     call      __intel_new_feature_proc_init                 #2.1
     stmxcsr   DWORD PTR [rsp]                               #2.1
     mov       eax, 14                                       #3.12
     or        DWORD PTR [rsp], 32832                        #2.1
     ldmxcsr   DWORD PTR [rsp]                               #2.1
     mov       rsp, rbp                                      #3.12
     pop       rbp                                           #3.12
     ret

【问题讨论】:

  • 如果您将函数名称更改为main2(或任何其他名称),则代码将更改 - 变得正常。这意味着main 是 icc 编译器的特殊名称,它会在此处生成额外的特殊代码
  • ICC 可能会为 main 生成模板代码,作为未优化的托管环境的一部分。将main 重命名为其他名称或尝试使用-ffreestandingmain 得到特殊待遇。
  • 谢谢。如果你们两个中的任何一个想把你的评论变成 A 并解释为什么 icc 对待 main 的方式不同,我会接受 A。
  • @OrenIshShalom:问题是关于 C++ 程序的。 Godbolt 链接显示该源代码正在编译为 C++,而不是 C。ICC 会为此发出与 C 程序相同的 asm(例如传递 -xc),但将标签任意更改为 C 是不合适的。我的回答已经提到了调用main 的程序的C++ 标准规则。 (ISO C 具有相同的规则,但您没有编辑我的答案以匹配您对问题的更改。)
  • @OrenIsh:不过,您编辑的其余部分都很好。

标签: c++ assembly x86 code-generation icc


【解决方案1】:

不知道为什么ICC选择将栈对齐2个缓存行:

and       rsp, -128                                     #2.1
sub       rsp, 128                                      #2.1

这很有趣。 L2 缓存有一个相邻行预取器,它喜欢将成对的行(在 128 字节对齐的组中)拉入 L2。但是 main 的栈帧通常不会被大量使用。也许在某些程序中分配了重要的变量。 (这也解释了设置rbp,以保存旧的 RSP,以便它可以在 ANDing 之后返回。gcc 在对齐堆栈的函数中使用 RBP 制作堆栈帧。)


其余是因为main()比较特殊,ICC默认启用-ffast-math。 (这是英特尔“肮脏”的小秘密之一,它可以自动向量化更多开箱即用的浮点代码。)

这包括在main 的顶部添加代码以设置 MXCSR(SSE 状态/控制寄存器)中的 DAZ / FTZ 位。有关这些位的更多信息,请参阅 Intel 的 x86 手册,但它们实际上并不复杂:

  • DAZ:非正规化为零:作为 SSE/AVX 指令的输入,非正规化被视为零。

  • FTZ:清零:当对 SSE/AVX 指令的结果进行四舍五入时,不正常的结果会清零。

相关:SSE "denormals are zeros" option

(ISO C++ 禁止程序回调到 main(),因此允许编译器将运行一次的东西放在 main 本身而不是 CRT 启动文件中。gcc/clang 与-ffast-math 指定用于设置 MXCSR 的 CRT 启动文件中的链接链接。但是当使用 gcc/clang 编译时,它只影响代码生成方面允许的优化。即当不同的临时对象时,将 FP add/mul 视为关联意思是真的不是。这与设置DAZ/FTZ完全无关)。


在这里,非正规被用作次正规的同义词:具有最小指数和有效数字的 FP 值,其中隐式前导位是 0 而不是 1。即幅度小于FLT_MIN or DBL_MIN 的值,最小可表示 标准化浮点/双精度。

https://en.wikipedia.org/wiki/Denormal_number.


产生非正常结果的指令可能慢得多:为了优化延迟,某些硬件中的快速路径假定结果是标准化的,如果结果无法标准化,则采用微码辅助。使用perf stat -e fp_assist.any 统计此类事件。

来自 Bruce Dawson 的优秀系列 FP 文章:That’s Not Normal–the Performance of Odd Floats。另外:

Agner Fog 进行了一些测试(参见他的 microarch pdf),并为 Haswell/Broadwell 报告:

下溢和次正规

当浮点运算接近于 下溢。在某些情况下,处理次正规数的成本很高 情况,因为次正常的结果由微码处理 例外。

Haswell 和 Broadwell 的罚分约为 124 时钟 在对正常数进行运算给出的所有情况下循环 结果不正常。乘法也有类似的惩罚 在正常数和次正常数之间,无论是否 结果正常或不正常。添加一个普通的没有惩罚 和一个次正规数,不管结果如何。没有罚款 用于上溢、下溢、无穷大或非数字结果。

如果“清零”,则可以避免对次正规数的处罚 模式和“denormals-are-zero”模式都在 MXCSR 中设置 注册。

因此,在某些情况下,现代 Intel CPU 即使在次规范的情况下也能避免惩罚,但是

【讨论】:

  • 你能解释一下为什么你说主堆栈帧没有被大量使用......我假设所有从 main 调用的函数(不包括异步调用和启动新线程)都使用“主堆栈”,所以如果他们在 128B 处对齐,但主要不是它们不会对齐,因为堆栈帧堆栈:)
  • @NoSenseEtAl:是的,它们都使用主调用堆栈(除非您启动新线程),但其他函数调用仅保持 16 字节对齐。我没有详细查看 ICC 的 main 在生成 call 时是否实际上具有 RSP 128 字节对齐,或者它是否只是获得 128 字节对齐然后正常运行,保留低于 16 字节的倍数.
猜你喜欢
  • 1970-01-01
  • 2013-12-14
  • 1970-01-01
  • 2023-03-07
  • 1970-01-01
  • 1970-01-01
  • 2013-04-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多