Apple clang -O1 优化不够?
Apple clang -O1 not optimizing enough?
我在 C:
中有这段代码
int main(void)
{
int a = 1 + 2;
return 0;
}
当我使用 GCC 9.3 -O0
标志编译的 objdump -x86-asm-syntax=intel -d a.out
时。0_1,我得到:
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 保留不必要的 push
和 pop
?
这可能是也可能不是一个单独的问题,但有以下代码:
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 代码?
我怀疑你是想看到添加发生。
int main(void)
{
int a = 1 + 2;
return 0;
}
但是通过优化说 -O2,你的死代码就消失了
00000000 <main>:
0: 2000 movs r0, #0
2: 4770 bx lr
变量 a 是局部的,它永远不会离开函数,它不依赖于函数之外的任何东西(全局变量、输入变量、来自被调用函数的 return 值等)。所以它没有任何功能目的它是死代码它什么都不做所以优化器可以自由地删除它并做了。
所以我假设您没有使用或较少使用优化,然后发现它太冗长了。
00000000 <main>:
0: cf 93 push r28
2: df 93 push r29
4: 00 d0 rcall .+0 ; 0x6 <main+0x6>
6: cd b7 in r28, 0x3d ; 61
8: de b7 in r29, 0x3e ; 62
a: 83 e0 ldi r24, 0x03 ; 3
c: 90 e0 ldi r25, 0x00 ; 0
e: 9a 83 std Y+2, r25 ; 0x02
10: 89 83 std Y+1, r24 ; 0x01
12: 80 e0 ldi r24, 0x00 ; 0
14: 90 e0 ldi r25, 0x00 ; 0
16: 0f 90 pop r0
18: 0f 90 pop r0
1a: df 91 pop r29
1c: cf 91 pop r28
1e: 08 95 ret
如果你想看到添加而不是首先发生,请不要使用 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
你可以弄清楚编译器return地址约定是什么,等等
但是你会遇到一些限制,试图让编译器为你做一些事情来学习 asm 同样你可以很容易地使用这些编译生成的代码
(使用 -save-temps 或 -S 或 disassemble 并输入(我更喜欢后者))但是你只能在你的操作系统上使用 high level/C 可调用函数。最终你会想要做一些裸机(首先在模拟器上)以获得最大的自由度并尝试你通常不能尝试的指令或者以一种困难的方式尝试它们或者你还不太了解如何使用它们操作系统在函数调用中的限制。 (请不要使用内联汇编,直到在路上或永远不要使用真正的汇编,理想情况下 assembler 而不是编译器 assemble 它,在路上然后尝试那些东西)。
一个编译器是为或默认使用堆栈帧而构建的,因此您需要告诉编译器忽略它。 -fomit 帧指针。请注意,其中一个或两个可以构建为默认没有帧指针。
../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 都有 "standard" 构建,因为它们都是可定制的,而且您下载的二进制文件有一些人对标准构建的看法)
您正在使用 main(),它有 return 0 或不是的东西并且它 can/will 携带其他行李。取决于编译器和设置。使用不是 main 的东西可以让您自由选择输入和输出,而不会警告您不符合 main() 的简短选择列表。
对于 gcc -O0 来说,理想情况下没有优化,尽管有时您会看到一些。 -O3 是 max 把你所有的都给我。 -O2 在历史上是人们居住的地方,除了 "I did it because everyone else is doing it" 之外没有其他原因。 -O1 是 gnu 的无人区,它有一些项目不在 -O0 中,但在 -O2 中没有很多好项目,因此在很大程度上取决于您的代码是否进入相关优化的 one/some与-O1。如果你的编译器甚至有一个 -O 选项,这些编号的优化东西只是一个预定义的列表 0 意味着这个列表 1 意味着那个列表等等。
没有理由期望任何两个编译器或具有不同选项的同一编译器从相同的源代码生成相同的代码。如果两个相互竞争的编译器能够在大多数情况下(如果不是全部)做到这一点,那么就会发生一些非常可疑的事情......同样没有理由期望每个编译器支持的优化列表,每个优化的作用等等,以匹配更少-O1 列表以匹配它们等等。
没有理由假设任何两个编译器或版本都符合相同目标的相同调用约定,处理器供应商创建推荐的调用约定,然后竞争编译器通常会遵守这一点,因为为什么不这样做,其他人都在这样做,甚至更好,我不必自己想办法,如果这个失败了,我可以责怪他们。
特别是在 C 中有很多实现定义的区域,在 C++ 中较少,但仍然...因此您对结果的期望以及编译器之间的相互比较也可能因此而不同。仅仅因为一个编译器以某种方式实现了一些代码并不意味着这就是该语言的工作方式,有时这就是编译器作者解释语言规范或有回旋余地的方式。
即使启用了完全优化,编译器必须提供的一切也没有理由假设编译器可以胜过人类。它是一种具有人类编程限制的算法,它无法胜过我们。根据经验,不难检查编译器的输出,有时是简单的函数,但通常是较大的函数,并发现遗漏的优化,或其他本可以完成的事情 "better" "better" 的某些意见。有时您发现编译器只是留下了一些您认为应该删除的东西,有时您是对的。
使用编译器开始学习汇编语言有如上所示的教育,即使有几十年的经验和对几十套汇编的涉猎languages/instruction,如果有调试的编译器可用我会通常从反汇编简单函数开始学习新的指令集,然后查找它们,然后开始从我在那里找到的内容中了解如何使用它。
经常从这个开始:
unsigned int fun ( unsigned int a )
{
return(a+5);
}
或
unsigned int fun ( unsigned int a, unsigned int b )
{
return(a+b);
}
然后从那里开始。同样,在编写 disassembler 或模拟器以学习指令集时,我经常依赖现有的 assembler,因为通常缺少处理器的文档,第一个 assemble 该处理器的 r 和编译器通常是通过直接访问硅片人员来完成的,然后后续人员也可以使用现有工具和文档来解决问题。
所以你正走在开始学习汇编语言的好道路上我对从哪些开始或不从哪些开始以改善体验和成功机会有强烈的意见,但我在 Stack Overflow 上经历了太多的战斗这周,我会放手。你可以看到我在这个答案中选择了一组指令集。即使您不了解它们,您也可能会弄清楚代码在做什么。 "standard" llvm 安装提供了从同一源代码为多个指令集输出汇编语言的能力。 gnu 方法是在编译工具链时选择目标(系列),并且编译的工具链仅限于 target/family 但您可以轻松地同时在计算机上安装多个 gnu 工具链,因为它们是 defaults/settings 用于相同的目标或不同的目标。无需学习构建工具、arm、avr、msp430、x86 和其他一些工具,即可轻松获得其中的一些。
我无法解释为什么当您实际上没有任何 return 代码时它不会从 main 中 return 归零。查看其他人的评论并阅读该语言的规范。 (或者将其作为一个单独的问题提出,或者查看是否已经回答)。
现在你说 Apple clang 不确定那个引用是什么我知道 Apple 已经在 llvm 中投入了大量的工作。或者您可能在 mac 或 Apple supplied/suggested 开发环境中,但查看维基百科和其他人,clang 不仅有 Apple,还有很多公司帮助,所以不确定那里的参考资料是什么。如果您使用的是 Apple 计算机,那么 apt gettable 就没有意义了,但是您仍然可以下载和安装许多基于预构建 gnu(和 llvm)的工具链,而不是尝试从源构建工具链(顺便说一句,这并不难。
我在 C:
中有这段代码int main(void)
{
int a = 1 + 2;
return 0;
}
当我使用 GCC 9.3 -O0
标志编译的 objdump -x86-asm-syntax=intel -d a.out
时。0_1,我得到:
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 保留不必要的 push
和 pop
?
这可能是也可能不是一个单独的问题,但有以下代码:
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 代码?
我怀疑你是想看到添加发生。
int main(void)
{
int a = 1 + 2;
return 0;
}
但是通过优化说 -O2,你的死代码就消失了
00000000 <main>:
0: 2000 movs r0, #0
2: 4770 bx lr
变量 a 是局部的,它永远不会离开函数,它不依赖于函数之外的任何东西(全局变量、输入变量、来自被调用函数的 return 值等)。所以它没有任何功能目的它是死代码它什么都不做所以优化器可以自由地删除它并做了。
所以我假设您没有使用或较少使用优化,然后发现它太冗长了。
00000000 <main>:
0: cf 93 push r28
2: df 93 push r29
4: 00 d0 rcall .+0 ; 0x6 <main+0x6>
6: cd b7 in r28, 0x3d ; 61
8: de b7 in r29, 0x3e ; 62
a: 83 e0 ldi r24, 0x03 ; 3
c: 90 e0 ldi r25, 0x00 ; 0
e: 9a 83 std Y+2, r25 ; 0x02
10: 89 83 std Y+1, r24 ; 0x01
12: 80 e0 ldi r24, 0x00 ; 0
14: 90 e0 ldi r25, 0x00 ; 0
16: 0f 90 pop r0
18: 0f 90 pop r0
1a: df 91 pop r29
1c: cf 91 pop r28
1e: 08 95 ret
如果你想看到添加而不是首先发生,请不要使用 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
你可以弄清楚编译器return地址约定是什么,等等
但是你会遇到一些限制,试图让编译器为你做一些事情来学习 asm 同样你可以很容易地使用这些编译生成的代码 (使用 -save-temps 或 -S 或 disassemble 并输入(我更喜欢后者))但是你只能在你的操作系统上使用 high level/C 可调用函数。最终你会想要做一些裸机(首先在模拟器上)以获得最大的自由度并尝试你通常不能尝试的指令或者以一种困难的方式尝试它们或者你还不太了解如何使用它们操作系统在函数调用中的限制。 (请不要使用内联汇编,直到在路上或永远不要使用真正的汇编,理想情况下 assembler 而不是编译器 assemble 它,在路上然后尝试那些东西)。
一个编译器是为或默认使用堆栈帧而构建的,因此您需要告诉编译器忽略它。 -fomit 帧指针。请注意,其中一个或两个可以构建为默认没有帧指针。
../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 都有 "standard" 构建,因为它们都是可定制的,而且您下载的二进制文件有一些人对标准构建的看法)
您正在使用 main(),它有 return 0 或不是的东西并且它 can/will 携带其他行李。取决于编译器和设置。使用不是 main 的东西可以让您自由选择输入和输出,而不会警告您不符合 main() 的简短选择列表。
对于 gcc -O0 来说,理想情况下没有优化,尽管有时您会看到一些。 -O3 是 max 把你所有的都给我。 -O2 在历史上是人们居住的地方,除了 "I did it because everyone else is doing it" 之外没有其他原因。 -O1 是 gnu 的无人区,它有一些项目不在 -O0 中,但在 -O2 中没有很多好项目,因此在很大程度上取决于您的代码是否进入相关优化的 one/some与-O1。如果你的编译器甚至有一个 -O 选项,这些编号的优化东西只是一个预定义的列表 0 意味着这个列表 1 意味着那个列表等等。
没有理由期望任何两个编译器或具有不同选项的同一编译器从相同的源代码生成相同的代码。如果两个相互竞争的编译器能够在大多数情况下(如果不是全部)做到这一点,那么就会发生一些非常可疑的事情......同样没有理由期望每个编译器支持的优化列表,每个优化的作用等等,以匹配更少-O1 列表以匹配它们等等。
没有理由假设任何两个编译器或版本都符合相同目标的相同调用约定,处理器供应商创建推荐的调用约定,然后竞争编译器通常会遵守这一点,因为为什么不这样做,其他人都在这样做,甚至更好,我不必自己想办法,如果这个失败了,我可以责怪他们。
特别是在 C 中有很多实现定义的区域,在 C++ 中较少,但仍然...因此您对结果的期望以及编译器之间的相互比较也可能因此而不同。仅仅因为一个编译器以某种方式实现了一些代码并不意味着这就是该语言的工作方式,有时这就是编译器作者解释语言规范或有回旋余地的方式。
即使启用了完全优化,编译器必须提供的一切也没有理由假设编译器可以胜过人类。它是一种具有人类编程限制的算法,它无法胜过我们。根据经验,不难检查编译器的输出,有时是简单的函数,但通常是较大的函数,并发现遗漏的优化,或其他本可以完成的事情 "better" "better" 的某些意见。有时您发现编译器只是留下了一些您认为应该删除的东西,有时您是对的。
使用编译器开始学习汇编语言有如上所示的教育,即使有几十年的经验和对几十套汇编的涉猎languages/instruction,如果有调试的编译器可用我会通常从反汇编简单函数开始学习新的指令集,然后查找它们,然后开始从我在那里找到的内容中了解如何使用它。
经常从这个开始:
unsigned int fun ( unsigned int a )
{
return(a+5);
}
或
unsigned int fun ( unsigned int a, unsigned int b )
{
return(a+b);
}
然后从那里开始。同样,在编写 disassembler 或模拟器以学习指令集时,我经常依赖现有的 assembler,因为通常缺少处理器的文档,第一个 assemble 该处理器的 r 和编译器通常是通过直接访问硅片人员来完成的,然后后续人员也可以使用现有工具和文档来解决问题。
所以你正走在开始学习汇编语言的好道路上我对从哪些开始或不从哪些开始以改善体验和成功机会有强烈的意见,但我在 Stack Overflow 上经历了太多的战斗这周,我会放手。你可以看到我在这个答案中选择了一组指令集。即使您不了解它们,您也可能会弄清楚代码在做什么。 "standard" llvm 安装提供了从同一源代码为多个指令集输出汇编语言的能力。 gnu 方法是在编译工具链时选择目标(系列),并且编译的工具链仅限于 target/family 但您可以轻松地同时在计算机上安装多个 gnu 工具链,因为它们是 defaults/settings 用于相同的目标或不同的目标。无需学习构建工具、arm、avr、msp430、x86 和其他一些工具,即可轻松获得其中的一些。
我无法解释为什么当您实际上没有任何 return 代码时它不会从 main 中 return 归零。查看其他人的评论并阅读该语言的规范。 (或者将其作为一个单独的问题提出,或者查看是否已经回答)。
现在你说 Apple clang 不确定那个引用是什么我知道 Apple 已经在 llvm 中投入了大量的工作。或者您可能在 mac 或 Apple supplied/suggested 开发环境中,但查看维基百科和其他人,clang 不仅有 Apple,还有很多公司帮助,所以不确定那里的参考资料是什么。如果您使用的是 Apple 计算机,那么 apt gettable 就没有意义了,但是您仍然可以下载和安装许多基于预构建 gnu(和 llvm)的工具链,而不是尝试从源构建工具链(顺便说一句,这并不难。