Apple clang -O1优化不够?

问题描述 投票:0回答:1

我在C中有此代码:

int main(void)
{
    int a = 1 + 2;
    return 0;
}

当我在GCC 9.3.0_1中使用objdump -x86-asm-syntax=intel -d a.out标志编译的-O0时,我得到:

0000000100000f9e _main:
100000f9e: 55                           push    rbp
100000f9f: 48 89 e5                     mov rbp, rsp
100000fa2: c7 45 fc 03 00 00 00         mov dword ptr [rbp - 4], 3
100000fa9: b8 00 00 00 00               mov eax, 0
100000fae: 5d                           pop rbp
100000faf: c3                           ret

并带有-O1标志:

0000000100000fc2 _main:
100000fc2: b8 00 00 00 00               mov eax, 0
100000fc7: c3                           ret

将未使用的变量a删除并进行堆栈管理。

但是,当我将Apple clang版本11.0.3与-O0-O1一起使用时,会得到

0000000100000fa0 _main:
100000fa0: 55                           push    rbp
100000fa1: 48 89 e5                     mov rbp, rsp
100000fa4: 31 c0                        xor eax, eax
100000fa6: c7 45 fc 00 00 00 00         mov dword ptr [rbp - 4], 0
100000fad: c7 45 f8 03 00 00 00         mov dword ptr [rbp - 8], 3
100000fb4: 5d                           pop rbp
100000fb5: c3                           ret

0000000100000fb0 _main:
100000fb0: 55                           push    rbp
100000fb1: 48 89 e5                     mov rbp, rsp
100000fb4: 31 c0                        xor eax, eax
100000fb6: 5d                           pop rbp
100000fb7: c3                           ret

分别。我从来没有像GCC中那样剥离过堆栈管理部分。为什么(Apple)Clang保留不必要的pushpop


这可能是也可能不是一个单独的问题,但是带有以下代码:

int main(void)
{
    // return 0;
}

GCC创建带有或不带有return 0;的相同ASM。但是,Clang -O0留下了额外的

100000fa6: c7 45 fc 00 00 00 00         mov dword ptr [rbp - 4], 0

[有return 0;时。

为什么Clang保留这些(可能是)冗余的ASM代码?

c gcc assembly clang compiler-optimization
1个回答
0
投票

我怀疑您正在尝试添加操作。

int main(void)
{
    int a = 1 + 2;
    return 0;
}

但是经过优化说-O2,您的无效代码消失了

00000000 <main>:
   0:   2000        movs    r0, #0
   2:   4770        bx  lr

变量a是局部变量,它从不离开函数,它不依赖于函数以外的任何东西(全局变量,输入变量,从被调用函数返回的值,等等)。因此,它没有功能性的用途,它是死代码,它什么也不做,因此优化器可以自由删除它并执行。

所以我假设您去使用了很少或更少的优化,然后发现它太冗长。

00000000 <main>:
   0:   04 41           mov r1, r4  
   2:   24 53           incd    r4      
   4:   21 83           decd    r1      
   6:   b4 40 03 00     mov #3, -4(r4)  ;#0x0003, 0xfffc(r4)
   a:   fc ff 
   c:   0f 43           clr r15     
   e:   21 53           incd    r1  

[如果您想看到添加发生,那么首先不要使用main(),它有行李,并且行李随工具链的不同而不同。因此,请尝试其他]

unsigned int fun ( unsigned int a, unsigned int b )
{
    return(a+b);
}

现在添加项依赖于外部项,因此编译器无法优化其中的任何一项。

00000000 <_fun>:
   0:   1d80 0002       mov 2(sp), r0
   4:   6d80 0004       add 4(sp), r0
   8:   0087            rts pc

如果我们想弄清楚哪个是a和哪个是b。

unsigned int fun ( unsigned int a, unsigned int b )
{
    return(a+(b<<1));
}

00000000 <_fun>:
   0:   1d80 0004       mov 4(sp), r0
   4:   0cc0            asl r0
   6:   6d80 0002       add 2(sp), r0
   a:   0087            rts pc

希望看到立即数

unsigned int fun ( unsigned int a )
{
    return(a+0x321);
}

00000000 <fun>:
   0:   8b 44 24 04             mov    eax,DWORD PTR [esp+0x4]
   4:   05 21 03 00 00          add    eax,0x321
   9:   c3                      ret 

您可以弄清楚编译器的返回地址约定是什么,等等。

但是您将遇到一些限制,试图让编译器为您学习asm做事情,同样,您可以轻松地获取这些编译生成的代码(使用-save-temps或-S或反汇编并键入(我更喜欢后者)),但是到目前为止,您只能在高级/ C可调用函数中使用操作系统。最终,您将需要做一些裸机操作(首先在模拟器上)以获得最大的自由度,并尝试通常无法尝试或很难尝试的指令,或者您还不了解如何在限制范围内使用函数调用中操作系统的名称。 (请不要使用内联汇编,否则,请不要使用内联汇编,最好使用真正的汇编,理想情况下,请使用汇编程序而不是编译器进行汇编,然后再尝试这些东西。)

[请在您考虑投票赞成此答案之前,就对发表评论的人进行投票,例如Jester,tadman,Michael,Peter等。


一个编译器是为使用堆栈帧而构建的,或者默认使用堆栈帧,因此您需要告诉编译器将其忽略。 -fomit-frame-pointer。请注意,可以将其中一个或两个默认设置为不具有框架指针。

../gcc-$GCCVER/configure --target=$TARGET --prefix=$PREFIX --without-headers --with-newlib  --with-gnu-as --with-gnu-ld --enable-languages='c' --enable-frame-pointer=no

(不要假设gcc或clang / llvm都具有“标准”构建,因为它们都是可定制的,并且您下载的二进制文件具有标准构建的观点)

您正在使用main(),这将返回0或不返回0,并且可以/将携带其他行李。取决于编译器和设置。使用非main的东西使您可以自由选择输入和输出,而不会警告您不符合main()的简短选择列表。

对于gcc -O0理想情况下没有优化,尽管有时您会看到一些优化。 -O3可以给我我所有的。 -O2在历史上是人们居住的地方,其原因除了“我之所以这样做是因为其他人都在做”。 -O1对于gnu来说是无人落地的,它有一些项不在-O0中,但在-O2中没有很多好项,因此,关于是否要进行与-相关的一项/某些优化,很大程度上取决于您的代码。 O1如果您的编译器甚至具有-O选项,这些编号为优化的东西只是一个预定义列表0表示此列表1表示该列表,依此类推。

没有理由期望任何两个编译器或具有不同选项的相同编译器从相同源产生相同代码。如果两个竞争的编译器能够在大多数时间(即使不是所有时间)都能够做到这一点,那么有些麻烦的事情正在发生...同样,没有理由期望每个编译器支持的优化列表,每个优化的工作等等,以减少匹配。 -O1列表以在它们之间进行匹配,依此类推。

没有理由假设任何两个编译器或版本都针对同一目标遵循相同的调用约定,处理器供应商现在创建推荐的调用约定,然后与之竞争的编译器通常会采用相同的调用约定之所以这样做,是因为为什么不这样做,其他所有人都在做,甚至更好,我不必自己弄清楚,如果这个失败了,我可以责怪他们。

特别是在C中有很多实现定义的领域,在C ++中则更少,但是仍然...因此,出于这个原因,您对出现的结果以及将编译器彼此进行比较的期望也可能有所不同。仅仅因为一个编译器以某种方式实现了一些代码并不意味着该语言的工作方式有时就意味着该编译器的作者解释了语言规范或留有余地。

即使启用了全面优化,编译器必须提供的所有内容也没有理由假定编译器的性能要优于人类。它是一种由人类编程的极限算法,它不能胜过我们。有经验的人,不难检查编译器的输出,有时是简单的函数,而通常是较大的函数,并发现错过的优化,或者对于“更好”的某些观点本可以“更好”完成的其他事情。有时您会发现编译器只留下一些您认为应该删除的内容,有时您是正确的。

如上所示,使用编译器开始学习汇编语言,甚至在数十年的经验和数十种汇编语言/指令集的掌握下,如果有可用的调试编译器,我通常会从反汇编开始简单的功能开始一个新的指令集,然后查找这些指令集,然后开始从中了解如何使用它。

经常从第一个开始:

unsigned int fun ( unsigned int a )
{
    return(a+5);
}

unsigned int fun ( unsigned int a, unsigned int b )
{
    return(a+b);
}

然后从那里去。同样,在编写反汇编程序或模拟器以有趣地学习指令集时,我经常依赖于现有的汇编程序,因为通常缺少处理器的文档,因此通常直接访问该处理器来完成该处理器的第一个汇编程序和编译器硅技术人员以及随后的硅技术人员也可以使用现有工具以及文档来解决问题。

我不能说为什么当您实际上没有在其中放置返回代码,查看其他人的注释并阅读该语言的规范时,为什么main不会返回零。 (或作为一个单独的问题提出)。

请在您考虑提高答案之前,先在评论中优先考虑回答此问题的人,包括杰斯特(Jester),塔德曼(Tadman),迈克尔(Michael),彼得(Peter)等。 (如果您宁愿投反对票或删除,也可以不理会其他人)。

© www.soinside.com 2019 - 2024. All rights reserved.