clang/LLVM 项目级优化
clang/LLVM project level optimization
所以我定期尝试 LLVM,因为我认为它应该优于 GNU。然后遗憾的是没有。
部分理论与它 link modules/objects 一起然后优化的能力有关,其中通常在每个 file/object 基础上进行优化。
我看到了如何为特定的默认目标构建,而不是使用通用目标
rm -rf llvm-project
git clone https://github.com/llvm/llvm-project.git
cd llvm-project
git checkout llvmorg-10.0.0
mkdir build
cd build
cmake -DLLVM_ENABLE_PROJECTS='clang;lld' -DCMAKE_CROSSCOMPILING=True -DCMAKE_INSTALL_PREFIX=/opt/llvm/llvm10armv6m -DLLVM_DEFAULT_TARGET_TRIPLE=armv6m-none-eabi -DLLVM_TARGET_ARCH=ARM -DLLVM_TARGETS_TO_BUILD=ARM -G "Unix Makefiles" ../llvm
make -j 8
make -j 4
make
sudo make install
和测试文件
test.c
unsigned int one ( void )
{
return(1);
}
unsigned int two ( void );
unsigned int testone ( void )
{
return(one());
}
unsigned int testtwo ( void )
{
return(two());
}
two.c
unsigned int two ( void )
{
return(2);
}
基本运行
clang -O2 -fomit-frame-pointer -c test.c -o test.o
llvm-objdump -D test.o
00000000 one:
0: 01 20 movs r0, #1
2: 70 47 bx lr
00000004 testone:
4: 01 20 movs r0, #1
6: 70 47 bx lr
00000008 testtwo:
8: 80 b5 push {r7, lr}
a: ff f7 fe ff bl #-4
e: 80 bd pop {r7, pc}
正如人们所期望的那样,one() 已内联到 testone() 中。
希望也能内联 testwo()。
clang -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
llc both.bc -o both.s
cat both.s
opt -O2 both.bc -o both.opt.bc
llc both.opt.bc -o both.opt.s
cat both.opt.s
给予
testone:
.fnstart
@ %bb.0: @ %entry
.save {r7, lr}
push {r7, lr}
bl one
pop {r7, pc}
testtwo:
.fnstart
@ %bb.0: @ %entry
.save {r7, lr}
push {r7, lr}
bl two
pop {r7, pc}
和
testone:
.fnstart
@ %bb.0: @ %entry
.save {r7, lr}
push {r7, lr}
bl one
pop {r7, pc}
testtwo:
.fnstart
@ %bb.0: @ %entry
.save {r7, lr}
push {r7, lr}
bl two
pop {r7, pc}
更糟。
opt -std-link-opts both.bc -o both.opt.bc
一样,没有更好
现在可以了
clang -O2 -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -O2 -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
opt -O2 both.bc -o both.opt.bc
llc both.opt.bc -o both.opt.s
cat both.opt.s
testone:
.fnstart
@ %bb.0: @ %entry
movs r0, #1
bx lr
testtwo:
.fnstart
@ %bb.0: @ %entry
movs r0, #2
bx lr
有人会认为不优化部分会给整体优化更多的肉来咀嚼。是的?尽管这表明并非如此。
clang -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
opt -O3 both.bc -o both.opt.bc
llc both.opt.bc -o both.opt.s
cat both.opt.s
testone:
.fnstart
@ %bb.0: @ %entry
.save {r7, lr}
push {r7, lr}
bl one
movs r0, #1
pop {r7, pc}
testtwo:
.fnstart
@ %bb.0: @ %entry
.save {r7, lr}
push {r7, lr}
bl two
movs r0, #2
pop {r7, pc}
-O3 也没有帮助,而且这个输出非常糟糕,它调用函数并内联它。那里发生了什么?!
llvm-dis both.opt.bc
cat both.opt.ll
; ModuleID = 'both.opt.bc'
source_filename = "llvm-link"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv6m-none-unknown-eabi"
; Function Attrs: noinline nounwind optnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
ret i32 1
}
; Function Attrs: noinline nounwind optnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
%call = call i32 @one()
ret i32 1
}
; Function Attrs: noinline nounwind optnone
define dso_local i32 @testtwo() local_unnamed_addr #0 {
entry:
%call = call i32 @two()
ret i32 2
}
; Function Attrs: noinline nounwind optnone
define dso_local i32 @two() local_unnamed_addr #0 {
entry:
ret i32 2
}
如何撤销它?
clang -O2 -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -O2 -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
llvm-dis both.bc
cat both.ll
opt -O3 both.bc -o both.opt.bc
llvm-dis both.opt.bc
cat both.opt.ll
给予
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
ret i32 1
}
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
ret i32 1
}
; Function Attrs: nounwind
define dso_local i32 @testtwo() local_unnamed_addr #1 {
entry:
%call = tail call i32 @two() #2
ret i32 %call
}
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @two() local_unnamed_addr #0 {
entry:
ret i32 2
}
和
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
ret i32 1
}
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
ret i32 1
}
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testtwo() local_unnamed_addr #0 {
entry:
ret i32 2
}
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @two() local_unnamed_addr #0 {
entry:
ret i32 2
}
那么您必须在 file/object 级别处处应用优化以使项目级别得到优化是否正确?
然后就是tail call或者leaf等优化的问题,如果不出意外testtwo:即使是第一种情况
clang -O2 -fomit-frame-pointer -c test.c -o test.o
可以简单地分支到 two() 并且不设置堆栈框架不执行任何操作。或者这是拇指的东西? b 够不着?
one:
0: b8 01 00 00 00 movl , %eax
5: c3 retq
testone:
10: b8 01 00 00 00 movl , %eax
15: c3 retq
testtwo:
20: e9 00 00 00 00 jmp 0 <testtwo+5>
在 gnu 中,linker 修补了任何分支到达或蹦床的模式问题
arm-none-eabi-gcc -c -O2 -mcpu=cortex-m0 test.c -o test.o
arm-none-eabi-objdump -D test.o
00000000 <one>:
0: 2001 movs r0, #1
2: 4770 bx lr
00000004 <testone>:
4: 2001 movs r0, #1
6: 4770 bx lr
00000008 <testtwo>:
8: b510 push {r4, lr}
a: f7ff fffe bl 0 <two>
e: bd10 pop {r4, pc}
好的,我接受纠正...
clang --version
clang version 10.0.0 (https://github.com/llvm/llvm-project.git d32170dbd5b0d54436537b6b75beaf44324e0c28)
Target: armv6m-none-unknown-eabi
Thread model: posix
InstalledDir: /opt/llvm/llvm10armv6m/bin
arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
我想问题是,如果有人想使用 llvm-link 和 opt 进行项目级优化,是对每个所需的单独项目进行优化,还是我缺少命令行选项。对进入源代码本身的编译器特定属性不感兴趣,不希望代码感染 gcc 或 llvm 细节。
在 gcc 5.x.x 之后代码变得更加臃肿希望 llvm 有机会但是每当我尝试这个(在项目上不仅仅是 10 行代码)gcc 最终执行的指令更少,and/or 更少的内存访问,等等。对于像上面的简单演示函数,除了一些例外,它们会产生 same/equivalent 输出。
为了从 clang/llvm 中获得更多,我是否缺少其他工具或命令行选项?
是不是这个工具的例子太琐碎了?
根据答案编辑
clang -c start.s -o start.o
clang -O2 -flto=thin -fomit-frame-pointer -c test.c
clang -O2 -flto=thin -fomit-frame-pointer -c two.c
ld.lld start.o test.o two.o -o test.elf
llvm-objdump -D test.elf
000110fc testtwo:
110fc: 02 20 movs r0, #2
110fe: 70 47 bx lr
00011100 two:
11100: 02 20 movs r0, #2
11102: 70 47 bx lr
所以去掉 -emit-llvm 并使用 lto 基本上可以得到想要的结果。
查看 bc 反汇编
clang -O2 -flto=thin -fomit-frame-pointer -c test.c
llvm-dis test.o
cat test.o.ll
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
ret i32 1
}
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
ret i32 1
}
; Function Attrs: nounwind
define dso_local i32 @testtwo() local_unnamed_addr #1 {
entry:
%call = tail call i32 @two() #3
ret i32 %call
}
enables/adds尾调用。我真的不喜欢将 compiler/shell 用作 linker(对于具有自己的 bootstrap 和 linker 脚本的嵌入式项目),llvm-ldd 的使用并不容易搞清楚或者基本上搞不清楚,但是ld.lld也支持tlo的东西,这样就搞定了。
答案实际上很简单:永远不要使用 llc / opt / llvm-link 来执行 "end-user" 项目级优化。这些是具有不同默认值、阈值等的开发人员端工具。基本上,它们只是各种 LLVM 工具箱的简单命令行前端。
为了执行正确的 link-time-optimization,您需要使用专用于此类任务的管道。基本上,使用 "clang -flto" 编译所有内容,然后通过 "clang -flto" 再次 linking 所有内容都可以。使用像 lld 这样的 LTO-aware linker 也是先决条件。
有关 ThinLTO 的更多信息也可以在这里找到:https://clang.llvm.org/docs/ThinLTO.html and http://blog.llvm.org/2016/06/thinlto-scalable-and-incremental-lto.html
所以我定期尝试 LLVM,因为我认为它应该优于 GNU。然后遗憾的是没有。
部分理论与它 link modules/objects 一起然后优化的能力有关,其中通常在每个 file/object 基础上进行优化。
我看到了如何为特定的默认目标构建,而不是使用通用目标
rm -rf llvm-project
git clone https://github.com/llvm/llvm-project.git
cd llvm-project
git checkout llvmorg-10.0.0
mkdir build
cd build
cmake -DLLVM_ENABLE_PROJECTS='clang;lld' -DCMAKE_CROSSCOMPILING=True -DCMAKE_INSTALL_PREFIX=/opt/llvm/llvm10armv6m -DLLVM_DEFAULT_TARGET_TRIPLE=armv6m-none-eabi -DLLVM_TARGET_ARCH=ARM -DLLVM_TARGETS_TO_BUILD=ARM -G "Unix Makefiles" ../llvm
make -j 8
make -j 4
make
sudo make install
和测试文件
test.c
unsigned int one ( void )
{
return(1);
}
unsigned int two ( void );
unsigned int testone ( void )
{
return(one());
}
unsigned int testtwo ( void )
{
return(two());
}
two.c
unsigned int two ( void )
{
return(2);
}
基本运行
clang -O2 -fomit-frame-pointer -c test.c -o test.o
llvm-objdump -D test.o
00000000 one:
0: 01 20 movs r0, #1
2: 70 47 bx lr
00000004 testone:
4: 01 20 movs r0, #1
6: 70 47 bx lr
00000008 testtwo:
8: 80 b5 push {r7, lr}
a: ff f7 fe ff bl #-4
e: 80 bd pop {r7, pc}
正如人们所期望的那样,one() 已内联到 testone() 中。
希望也能内联 testwo()。
clang -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
llc both.bc -o both.s
cat both.s
opt -O2 both.bc -o both.opt.bc
llc both.opt.bc -o both.opt.s
cat both.opt.s
给予
testone:
.fnstart
@ %bb.0: @ %entry
.save {r7, lr}
push {r7, lr}
bl one
pop {r7, pc}
testtwo:
.fnstart
@ %bb.0: @ %entry
.save {r7, lr}
push {r7, lr}
bl two
pop {r7, pc}
和
testone:
.fnstart
@ %bb.0: @ %entry
.save {r7, lr}
push {r7, lr}
bl one
pop {r7, pc}
testtwo:
.fnstart
@ %bb.0: @ %entry
.save {r7, lr}
push {r7, lr}
bl two
pop {r7, pc}
更糟。
opt -std-link-opts both.bc -o both.opt.bc
一样,没有更好
现在可以了
clang -O2 -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -O2 -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
opt -O2 both.bc -o both.opt.bc
llc both.opt.bc -o both.opt.s
cat both.opt.s
testone:
.fnstart
@ %bb.0: @ %entry
movs r0, #1
bx lr
testtwo:
.fnstart
@ %bb.0: @ %entry
movs r0, #2
bx lr
有人会认为不优化部分会给整体优化更多的肉来咀嚼。是的?尽管这表明并非如此。
clang -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
opt -O3 both.bc -o both.opt.bc
llc both.opt.bc -o both.opt.s
cat both.opt.s
testone:
.fnstart
@ %bb.0: @ %entry
.save {r7, lr}
push {r7, lr}
bl one
movs r0, #1
pop {r7, pc}
testtwo:
.fnstart
@ %bb.0: @ %entry
.save {r7, lr}
push {r7, lr}
bl two
movs r0, #2
pop {r7, pc}
-O3 也没有帮助,而且这个输出非常糟糕,它调用函数并内联它。那里发生了什么?!
llvm-dis both.opt.bc
cat both.opt.ll
; ModuleID = 'both.opt.bc'
source_filename = "llvm-link"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv6m-none-unknown-eabi"
; Function Attrs: noinline nounwind optnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
ret i32 1
}
; Function Attrs: noinline nounwind optnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
%call = call i32 @one()
ret i32 1
}
; Function Attrs: noinline nounwind optnone
define dso_local i32 @testtwo() local_unnamed_addr #0 {
entry:
%call = call i32 @two()
ret i32 2
}
; Function Attrs: noinline nounwind optnone
define dso_local i32 @two() local_unnamed_addr #0 {
entry:
ret i32 2
}
如何撤销它?
clang -O2 -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -O2 -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
llvm-dis both.bc
cat both.ll
opt -O3 both.bc -o both.opt.bc
llvm-dis both.opt.bc
cat both.opt.ll
给予
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
ret i32 1
}
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
ret i32 1
}
; Function Attrs: nounwind
define dso_local i32 @testtwo() local_unnamed_addr #1 {
entry:
%call = tail call i32 @two() #2
ret i32 %call
}
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @two() local_unnamed_addr #0 {
entry:
ret i32 2
}
和
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
ret i32 1
}
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
ret i32 1
}
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testtwo() local_unnamed_addr #0 {
entry:
ret i32 2
}
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @two() local_unnamed_addr #0 {
entry:
ret i32 2
}
那么您必须在 file/object 级别处处应用优化以使项目级别得到优化是否正确?
然后就是tail call或者leaf等优化的问题,如果不出意外testtwo:即使是第一种情况
clang -O2 -fomit-frame-pointer -c test.c -o test.o
可以简单地分支到 two() 并且不设置堆栈框架不执行任何操作。或者这是拇指的东西? b 够不着?
one:
0: b8 01 00 00 00 movl , %eax
5: c3 retq
testone:
10: b8 01 00 00 00 movl , %eax
15: c3 retq
testtwo:
20: e9 00 00 00 00 jmp 0 <testtwo+5>
在 gnu 中,linker 修补了任何分支到达或蹦床的模式问题
arm-none-eabi-gcc -c -O2 -mcpu=cortex-m0 test.c -o test.o
arm-none-eabi-objdump -D test.o
00000000 <one>:
0: 2001 movs r0, #1
2: 4770 bx lr
00000004 <testone>:
4: 2001 movs r0, #1
6: 4770 bx lr
00000008 <testtwo>:
8: b510 push {r4, lr}
a: f7ff fffe bl 0 <two>
e: bd10 pop {r4, pc}
好的,我接受纠正...
clang --version
clang version 10.0.0 (https://github.com/llvm/llvm-project.git d32170dbd5b0d54436537b6b75beaf44324e0c28)
Target: armv6m-none-unknown-eabi
Thread model: posix
InstalledDir: /opt/llvm/llvm10armv6m/bin
arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
我想问题是,如果有人想使用 llvm-link 和 opt 进行项目级优化,是对每个所需的单独项目进行优化,还是我缺少命令行选项。对进入源代码本身的编译器特定属性不感兴趣,不希望代码感染 gcc 或 llvm 细节。
在 gcc 5.x.x 之后代码变得更加臃肿希望 llvm 有机会但是每当我尝试这个(在项目上不仅仅是 10 行代码)gcc 最终执行的指令更少,and/or 更少的内存访问,等等。对于像上面的简单演示函数,除了一些例外,它们会产生 same/equivalent 输出。
为了从 clang/llvm 中获得更多,我是否缺少其他工具或命令行选项?
是不是这个工具的例子太琐碎了?
根据答案编辑
clang -c start.s -o start.o
clang -O2 -flto=thin -fomit-frame-pointer -c test.c
clang -O2 -flto=thin -fomit-frame-pointer -c two.c
ld.lld start.o test.o two.o -o test.elf
llvm-objdump -D test.elf
000110fc testtwo:
110fc: 02 20 movs r0, #2
110fe: 70 47 bx lr
00011100 two:
11100: 02 20 movs r0, #2
11102: 70 47 bx lr
所以去掉 -emit-llvm 并使用 lto 基本上可以得到想要的结果。
查看 bc 反汇编
clang -O2 -flto=thin -fomit-frame-pointer -c test.c
llvm-dis test.o
cat test.o.ll
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
ret i32 1
}
; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
ret i32 1
}
; Function Attrs: nounwind
define dso_local i32 @testtwo() local_unnamed_addr #1 {
entry:
%call = tail call i32 @two() #3
ret i32 %call
}
enables/adds尾调用。我真的不喜欢将 compiler/shell 用作 linker(对于具有自己的 bootstrap 和 linker 脚本的嵌入式项目),llvm-ldd 的使用并不容易搞清楚或者基本上搞不清楚,但是ld.lld也支持tlo的东西,这样就搞定了。
答案实际上很简单:永远不要使用 llc / opt / llvm-link 来执行 "end-user" 项目级优化。这些是具有不同默认值、阈值等的开发人员端工具。基本上,它们只是各种 LLVM 工具箱的简单命令行前端。
为了执行正确的 link-time-optimization,您需要使用专用于此类任务的管道。基本上,使用 "clang -flto" 编译所有内容,然后通过 "clang -flto" 再次 linking 所有内容都可以。使用像 lld 这样的 LTO-aware linker 也是先决条件。
有关 ThinLTO 的更多信息也可以在这里找到:https://clang.llvm.org/docs/ThinLTO.html and http://blog.llvm.org/2016/06/thinlto-scalable-and-incremental-lto.html